@letar/forms 1.0.3 → 1.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/index.js CHANGED
@@ -1,26 +1,29 @@
1
+ import { FieldFileUpload, FieldColorPicker, FieldOTPInput, FieldPinInput, FieldCity, FieldAddress, FieldPhone } from './chunk-GOELIS6T.js';
1
2
  import { FormSyncStatus, FormOfflineIndicator, useOfflineForm } from './chunk-4V6WBJ76.js';
2
3
  export { FormOfflineIndicator, FormSyncStatus, useOfflineForm, useOfflineStatus, useSyncQueue } from './chunk-4V6WBJ76.js';
3
- import { useFormI18n, getLocalizedValue, useLocalizedOptions } from './chunk-7FEQFDJ7.js';
4
+ import { FieldRichText, FieldEditable, FieldMaskedInput, FieldPasswordStrength, FieldPassword, FieldTextarea, FieldString } from './chunk-M2PNAAIR.js';
5
+ export { FieldString } from './chunk-M2PNAAIR.js';
6
+ import { FieldRating, FieldSlider, FieldPercentage, FieldCurrency, FieldNumberInput, FieldNumber } from './chunk-XKKJKYWZ.js';
7
+ export { FieldNumber } from './chunk-XKKJKYWZ.js';
8
+ import { FieldSchedule, FieldDuration, FieldTime, FieldDateTimePicker, FieldDateRange, FieldDate } from './chunk-KUNT5MSU.js';
9
+ import { FieldTags, FieldSegmentedGroup, FieldRadioCard, FieldRadioGroup, FieldListbox, FieldAutocomplete, FieldCombobox, FieldCascadingSelect, FieldNativeSelect, FieldSelect, FieldCheckboxCard } from './chunk-PJETA6YN.js';
10
+ export { FieldCombobox, FieldListbox, FieldSegmentedGroup, FieldSelect } from './chunk-PJETA6YN.js';
11
+ import { FieldSwitch, FieldCheckbox } from './chunk-6QOPSQ3Z.js';
12
+ import { useDeclarativeForm, useFormGroup, getZodConstraints, FormGroup, DeclarativeFormContext, unwrapSchema } from './chunk-HWVOFWAT.js';
13
+ export { DeclarativeFormContext, FieldLabel, FieldTooltip, FormGroup, useAsyncSearch, useDebounce, useDeclarativeField, useDeclarativeForm, useDeclarativeFormOptional, useFormGroup } from './chunk-HWVOFWAT.js';
4
14
  export { FormI18nProvider, getLocalizedValue, useFormI18n, useLocalizedOptions } from './chunk-7FEQFDJ7.js';
5
15
  import { createFormHookContexts, createFormHook } from '@tanstack/react-form';
6
- import { createContext, forwardRef, memo, useMemo, useCallback, useContext, Fragment, useState, useRef, useEffect, Children, isValidElement, useSyncExternalStore, lazy, Suspense } from 'react';
16
+ import { createContext, forwardRef, useContext, Fragment, useState, useRef, useMemo, useCallback, useEffect, Children, isValidElement, useSyncExternalStore, lazy, Suspense } from 'react';
7
17
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
8
- import { Field, Stack, Box, Text, HStack, Button, Input, IconButton, Steps, ButtonGroup, VStack, Heading, Alert, List, FileUpload, Icon, parseColor, ColorPicker, Portal, PinInput, Spinner, Group, TagsInput, SegmentGroup, RadioCard, RadioGroup, Listbox, Combobox, useFilter, createListCollection, NativeSelect, Select, Switch, Fieldset, CheckboxGroup, Flex, CheckboxCard, Checkbox, NumberInput, Menu, RatingGroup, Slider, For, Editable, Progress, InputGroup, Textarea, Tooltip, Circle, useFileUploadContext, Float, Popover, Center, Image, Dialog, CloseButton, Skeleton } from '@chakra-ui/react';
18
+ import { Field, IconButton, Button, Steps, ButtonGroup, Box, VStack, HStack, Heading, Alert, List, Text, Dialog, Portal, CloseButton, Skeleton } from '@chakra-ui/react';
9
19
  import { useRouter } from 'next/navigation';
10
- import { LuCheck, LuUpload, LuCalendar, LuChevronDown, LuEyeOff, LuEye, LuX, LuCircleHelp, LuFile, LuUnlink, LuLink, LuImage, LuRedo, LuUndo, LuQuote, LuListOrdered, LuList, LuHeading3, LuHeading2, LuHeading1, LuCode, LuStrikethrough, LuUnderline, LuItalic, LuBold } from 'react-icons/lu';
11
- import TiptapImage from '@tiptap/extension-image';
12
- import { Link } from '@tiptap/extension-link';
13
- import Placeholder from '@tiptap/extension-placeholder';
14
- import Underline from '@tiptap/extension-underline';
15
- import { useEditor, EditorContent } from '@tiptap/react';
16
- import StarterKit from '@tiptap/starter-kit';
17
- import { withMask } from 'use-mask-input';
18
20
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
19
21
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
20
22
  import { CSS } from '@dnd-kit/utilities';
21
23
  import JsonView from '@uiw/react-json-view';
22
24
  import { githubDarkTheme } from '@uiw/react-json-view/githubDark';
23
25
  import { githubLightTheme } from '@uiw/react-json-view/githubLight';
26
+ import { LuCheck } from 'react-icons/lu';
24
27
  import { AnimatePresence, motion } from 'framer-motion';
25
28
  import { z } from 'zod/v4';
26
29
 
@@ -83,18 +86,6 @@ var { useAppForm, withForm } = createFormHook({
83
86
  fieldComponents,
84
87
  formComponents
85
88
  });
86
- var FormGroupContext = createContext(null);
87
- function FormGroup({ name, children }) {
88
- const parentContext = useContext(FormGroupContext);
89
- const contextValue = {
90
- originalName: name,
91
- name: parentContext ? `${parentContext.name}.${name}` : name
92
- };
93
- return /* @__PURE__ */ jsx(FormGroupContext.Provider, { value: contextValue, children });
94
- }
95
- function useFormGroup() {
96
- return useContext(FormGroupContext);
97
- }
98
89
  var FormFieldContext = createContext(null);
99
90
  function FormField({ name, children }) {
100
91
  const formGroupContext = useFormGroup();
@@ -189,17 +180,6 @@ function useFormGroupList() {
189
180
  function useFormGroupListItem() {
190
181
  return useContext(FormGroupListItemContext);
191
182
  }
192
- var DeclarativeFormContext = createContext(null);
193
- function useDeclarativeForm() {
194
- const context = useContext(DeclarativeFormContext);
195
- if (!context) {
196
- throw new Error("useDeclarativeForm must be used inside a Form component");
197
- }
198
- return context;
199
- }
200
- function useDeclarativeFormOptional() {
201
- return useContext(DeclarativeFormContext);
202
- }
203
183
  function DirtyGuard({
204
184
  message = "You have unsaved changes. Are you sure you want to leave?",
205
185
  dialogTitle = "Unsaved changes",
@@ -262,4203 +242,133 @@ function DirtyGuard({
262
242
  if (event.ctrlKey || event.metaKey) {
263
243
  return;
264
244
  }
265
- const shouldBlock = onBlock?.();
266
- if (shouldBlock === false) {
267
- return;
268
- }
269
- event.preventDefault();
270
- event.stopPropagation();
271
- pendingHref.current = href;
272
- setShowDialog(true);
273
- };
274
- document.addEventListener("click", handleClick, { capture: true });
275
- return () => document.removeEventListener("click", handleClick, { capture: true });
276
- }, [enabled, onBlock, checkIsDirty]);
277
- const handleConfirm = useCallback(() => {
278
- setShowDialog(false);
279
- if (pendingHref.current) {
280
- form.reset();
281
- router.push(pendingHref.current);
282
- pendingHref.current = null;
283
- }
284
- }, [form, router]);
285
- const handleCancel = useCallback(() => {
286
- setShowDialog(false);
287
- pendingHref.current = null;
288
- }, []);
289
- if (!showDialog) {
290
- return null;
291
- }
292
- return /* @__PURE__ */ jsx(
293
- "div",
294
- {
295
- style: {
296
- position: "fixed",
297
- inset: 0,
298
- zIndex: 9999,
299
- display: "flex",
300
- alignItems: "center",
301
- justifyContent: "center",
302
- backgroundColor: "rgba(0, 0, 0, 0.5)"
303
- },
304
- onClick: handleCancel,
305
- children: /* @__PURE__ */ jsxs(
306
- "div",
307
- {
308
- style: {
309
- backgroundColor: "var(--chakra-colors-bg-panel, white)",
310
- borderRadius: "12px",
311
- padding: "24px",
312
- maxWidth: "400px",
313
- width: "90%",
314
- boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)"
315
- },
316
- onClick: (e) => e.stopPropagation(),
317
- children: [
318
- /* @__PURE__ */ jsx(
319
- "h2",
320
- {
321
- style: {
322
- margin: "0 0 8px 0",
323
- fontSize: "1.125rem",
324
- fontWeight: 600,
325
- color: "var(--chakra-colors-fg, inherit)"
326
- },
327
- children: dialogTitle
328
- }
329
- ),
330
- /* @__PURE__ */ jsx(
331
- "p",
332
- {
333
- style: {
334
- margin: "0 0 24px 0",
335
- fontSize: "0.875rem",
336
- color: "var(--chakra-colors-fg-muted, #666)"
337
- },
338
- children: dialogDescription
339
- }
340
- ),
341
- /* @__PURE__ */ jsxs(
342
- "div",
343
- {
344
- style: {
345
- display: "flex",
346
- gap: "12px",
347
- justifyContent: "flex-end"
348
- },
349
- children: [
350
- /* @__PURE__ */ jsx(
351
- "button",
352
- {
353
- onClick: handleCancel,
354
- style: {
355
- padding: "8px 16px",
356
- borderRadius: "6px",
357
- border: "1px solid var(--chakra-colors-border, #e2e8f0)",
358
- backgroundColor: "transparent",
359
- cursor: "pointer",
360
- fontSize: "0.875rem",
361
- fontWeight: 500
362
- },
363
- children: cancelText
364
- }
365
- ),
366
- /* @__PURE__ */ jsx(
367
- "button",
368
- {
369
- onClick: handleConfirm,
370
- style: {
371
- padding: "8px 16px",
372
- borderRadius: "6px",
373
- border: "none",
374
- backgroundColor: "var(--chakra-colors-red-500, #e53e3e)",
375
- color: "white",
376
- cursor: "pointer",
377
- fontSize: "0.875rem",
378
- fontWeight: 500
379
- },
380
- children: confirmText
381
- }
382
- )
383
- ]
384
- }
385
- )
386
- ]
387
- }
388
- )
389
- }
390
- );
391
- }
392
- function FieldTooltip({ title, description, example, impact }) {
393
- return /* @__PURE__ */ jsxs(Tooltip.Root, { openDelay: 200, positioning: { placement: "top" }, children: [
394
- /* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
395
- Circle,
396
- {
397
- size: "5",
398
- cursor: "help",
399
- color: "fg.muted",
400
- _hover: { color: "colorPalette.fg" },
401
- transition: "color 0.2s",
402
- display: "inline-flex",
403
- children: /* @__PURE__ */ jsx(LuCircleHelp, { size: 16 })
404
- }
405
- ) }),
406
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Tooltip.Positioner, { children: /* @__PURE__ */ jsxs(Tooltip.Content, { children: [
407
- /* @__PURE__ */ jsx(Tooltip.Arrow, { children: /* @__PURE__ */ jsx(Tooltip.ArrowTip, {}) }),
408
- /* @__PURE__ */ jsxs(VStack, { align: "start", gap: 2, maxW: "280px", p: 1, children: [
409
- title && /* @__PURE__ */ jsx(Text, { fontWeight: "semibold", fontSize: "sm", children: title }),
410
- /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: description }),
411
- example && /* @__PURE__ */ jsxs(Box, { bg: "bg.emphasized", px: 2, py: 1, borderRadius: "md", w: "full", children: [
412
- /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "Example:" }),
413
- /* @__PURE__ */ jsxs(Text, { fontSize: "sm", fontStyle: "italic", children: [
414
- '"',
415
- example,
416
- '"'
417
- ] })
418
- ] }),
419
- impact && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "green.fg", children: impact })
420
- ] })
421
- ] }) }) })
422
- ] });
423
- }
424
- function FieldLabel({ label, tooltip, required }) {
425
- if (!label) {
426
- return null;
427
- }
428
- return /* @__PURE__ */ jsxs(Field.Label, { children: [
429
- tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
430
- /* @__PURE__ */ jsx("span", { children: label }),
431
- /* @__PURE__ */ jsx(FieldTooltip, { ...tooltip })
432
- ] }) : label,
433
- required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
434
- ] });
435
- }
436
-
437
- // src/lib/declarative/form-fields/base/field-utils.ts
438
- function formatFieldErrors(errors) {
439
- return errors.map((e) => extractErrorMessage(e)).filter(Boolean).join(", ");
440
- }
441
- function extractErrorMessage(e) {
442
- if (typeof e === "string") {
443
- return e;
444
- }
445
- if (e === null || e === void 0) {
446
- return "";
447
- }
448
- if (typeof e === "object") {
449
- if (Array.isArray(e)) {
450
- return e.map((item) => {
451
- if (typeof item === "object" && item && "message" in item) {
452
- return item.message;
453
- }
454
- if (typeof item === "string") {
455
- return item;
456
- }
457
- return "";
458
- }).filter(Boolean).join(", ");
459
- }
460
- if ("message" in e && typeof e.message === "string") {
461
- return e.message;
462
- }
463
- if ("issues" in e && Array.isArray(e.issues)) {
464
- return e.issues.map((issue) => {
465
- if (typeof issue === "object" && issue && "message" in issue) {
466
- return issue.message;
467
- }
468
- return "";
469
- }).filter(Boolean).join(", ");
470
- }
471
- if ("fieldErrors" in e && typeof e.fieldErrors === "object" && e.fieldErrors) {
472
- const fieldErrors = e.fieldErrors;
473
- return Object.values(fieldErrors).flat().filter(Boolean).join(", ");
474
- }
475
- if ("_errors" in e && Array.isArray(e._errors)) {
476
- return e._errors.filter(Boolean).join(", ");
477
- }
478
- try {
479
- const json = JSON.stringify(e);
480
- if (json === "{}" || json.length > 200) {
481
- return "";
482
- }
483
- if (process.env.NODE_ENV === "development") {
484
- console.warn("[form-components] Unknown error format:", e);
485
- }
486
- return json;
487
- } catch {
488
- return "";
489
- }
490
- }
491
- return String(e);
492
- }
493
- function hasFieldErrors(errors) {
494
- return Boolean(errors && errors.length > 0);
495
- }
496
- function getFieldErrors(field) {
497
- const errors = field.state.meta.errors ?? [];
498
- const hasError = hasFieldErrors(errors);
499
- const errorMessage = hasError ? formatFieldErrors(errors) : "";
500
- return { errors, hasError, errorMessage };
501
- }
502
-
503
- // src/lib/declarative/constraint-hints.ts
504
- var EN_TRANSLATIONS = {
505
- string_exact: "Exactly {n} {chars}",
506
- string_range: "From {min} to {max} characters",
507
- string_max: "Maximum {n} {chars}",
508
- string_min: "Minimum {n} {chars}",
509
- number_range: "From {min} to {max}{suffix}",
510
- number_max: "Maximum {max}{suffix}",
511
- number_min: "Minimum {min}{suffix}",
512
- number_integer: "Integer",
513
- number_integer_suffix: " (integer)",
514
- date_range: "From {min} to {max}",
515
- date_after: "Not before {min}",
516
- date_before: "Not after {max}",
517
- array_exact: "Exactly {n} {items}",
518
- array_range: "From {min} to {max} items",
519
- array_max: "Maximum {n} {items}",
520
- array_min: "Minimum {n} {items}"
521
- };
522
- var RU_TRANSLATIONS = {
523
- string_exact: "\u0420\u043E\u0432\u043D\u043E {n} {chars}",
524
- string_range: "\u041E\u0442 {min} \u0434\u043E {max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
525
- string_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {n} {chars}",
526
- string_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {n} {chars}",
527
- number_range: "\u041E\u0442 {min} \u0434\u043E {max}{suffix}",
528
- number_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max}{suffix}",
529
- number_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min}{suffix}",
530
- number_integer: "\u0426\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E",
531
- number_integer_suffix: " (\u0446\u0435\u043B\u043E\u0435)",
532
- date_range: "\u0421 {min} \u043F\u043E {max}",
533
- date_after: "\u041D\u0435 \u0440\u0430\u043D\u0435\u0435 {min}",
534
- date_before: "\u041D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {max}",
535
- array_exact: "\u0420\u043E\u0432\u043D\u043E {n} {items}",
536
- array_range: "\u041E\u0442 {min} \u0434\u043E {max} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
537
- array_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {n} {items}",
538
- array_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {n} {items}"
539
- };
540
- var BUILTIN_TRANSLATIONS = {
541
- en: EN_TRANSLATIONS,
542
- ru: RU_TRANSLATIONS
543
- };
544
- var CHAR_PLURALS = {
545
- en: { one: "character", other: "characters" },
546
- ru: { one: "\u0441\u0438\u043C\u0432\u043E\u043B", few: "\u0441\u0438\u043C\u0432\u043E\u043B\u0430", many: "\u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432", other: "\u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432" }
547
- };
548
- var ITEM_PLURALS = {
549
- en: { one: "item", other: "items" },
550
- ru: { one: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442", few: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0430", many: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432", other: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432" }
551
- };
552
- function pluralizeWord(n, locale, plurals) {
553
- const lang = locale.split("-")[0];
554
- const forms = plurals[lang] ?? plurals.en;
555
- const rule = new Intl.PluralRules(locale).select(n);
556
- return forms[rule] ?? forms.other;
557
- }
558
- function formatNumber(n, locale) {
559
- if (Number.isInteger(n)) {
560
- return new Intl.NumberFormat(locale).format(n);
561
- }
562
- return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(n);
563
- }
564
- function formatDate(dateStr, locale) {
565
- try {
566
- const date = new Date(dateStr);
567
- return new Intl.DateTimeFormat(locale, {
568
- day: "numeric",
569
- month: "long",
570
- year: "numeric"
571
- }).format(date);
572
- } catch {
573
- return dateStr;
574
- }
575
- }
576
- function template(str, vars) {
577
- return str.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
578
- }
579
- function getTranslations(locale, custom) {
580
- const lang = locale.split("-")[0];
581
- const base = BUILTIN_TRANSLATIONS[lang] ?? BUILTIN_TRANSLATIONS[locale] ?? EN_TRANSLATIONS;
582
- return base;
583
- }
584
- function generateConstraintHint(constraints, locale = "en", customTranslations) {
585
- if (!constraints) {
586
- return void 0;
587
- }
588
- const t = getTranslations(locale);
589
- switch (constraints.schemaType) {
590
- case "string":
591
- return generateStringHint(constraints.string, locale, t);
592
- case "number":
593
- return generateNumberHint(constraints.number, locale, t);
594
- case "date":
595
- return generateDateHint(constraints.date, locale, t);
596
- case "array":
597
- return generateArrayHint(constraints.array, locale, t);
598
- default:
599
- return void 0;
600
- }
601
- }
602
- function generateStringHint(constraints, locale, t) {
603
- if (!constraints) return void 0;
604
- const { minLength, maxLength, inputType } = constraints;
605
- if (inputType === "email" || inputType === "url") {
606
- if (maxLength) {
607
- return template(t.string_max, { n: maxLength, chars: pluralizeWord(maxLength, locale, CHAR_PLURALS) });
608
- }
609
- return void 0;
610
- }
611
- if (minLength !== void 0 && maxLength !== void 0) {
612
- if (minLength === maxLength) {
613
- return template(t.string_exact, { n: minLength, chars: pluralizeWord(minLength, locale, CHAR_PLURALS) });
614
- }
615
- return template(t.string_range, { min: minLength, max: maxLength });
616
- }
617
- if (maxLength !== void 0) {
618
- return template(t.string_max, { n: maxLength, chars: pluralizeWord(maxLength, locale, CHAR_PLURALS) });
619
- }
620
- if (minLength !== void 0) {
621
- return template(t.string_min, { n: minLength, chars: pluralizeWord(minLength, locale, CHAR_PLURALS) });
622
- }
623
- return void 0;
624
- }
625
- function generateNumberHint(constraints, locale, t) {
626
- if (!constraints) return void 0;
627
- const { min, max, isInteger } = constraints;
628
- const suffix = isInteger ? t.number_integer_suffix : "";
629
- if (min !== void 0 && max !== void 0) {
630
- return template(t.number_range, { min: formatNumber(min, locale), max: formatNumber(max, locale), suffix });
631
- }
632
- if (max !== void 0) {
633
- return template(t.number_max, { max: formatNumber(max, locale), suffix });
634
- }
635
- if (min !== void 0) {
636
- return template(t.number_min, { min: formatNumber(min, locale), suffix });
637
- }
638
- if (isInteger) {
639
- return t.number_integer;
640
- }
641
- return void 0;
642
- }
643
- function generateDateHint(constraints, locale, t) {
644
- if (!constraints) return void 0;
645
- const { min, max } = constraints;
646
- if (min && max) {
647
- return template(t.date_range, { min: formatDate(min, locale), max: formatDate(max, locale) });
648
- }
649
- if (min) {
650
- return template(t.date_after, { min: formatDate(min, locale) });
651
- }
652
- if (max) {
653
- return template(t.date_before, { max: formatDate(max, locale) });
654
- }
655
- return void 0;
656
- }
657
- function generateArrayHint(constraints, locale, t) {
658
- if (!constraints) return void 0;
659
- const { minItems, maxItems } = constraints;
660
- if (minItems !== void 0 && maxItems !== void 0) {
661
- if (minItems === maxItems) {
662
- return template(t.array_exact, { n: minItems, items: pluralizeWord(minItems, locale, ITEM_PLURALS) });
663
- }
664
- return template(t.array_range, { min: minItems, max: maxItems });
665
- }
666
- if (maxItems !== void 0) {
667
- return template(t.array_max, { n: maxItems, items: pluralizeWord(maxItems, locale, ITEM_PLURALS) });
668
- }
669
- if (minItems !== void 0) {
670
- return template(t.array_min, { n: minItems, items: pluralizeWord(minItems, locale, ITEM_PLURALS) });
671
- }
672
- return void 0;
673
- }
674
-
675
- // src/lib/declarative/zod-utils.ts
676
- function unwrapSchema(schema) {
677
- if (!schema?._zod?.def) {
678
- return schema;
679
- }
680
- const type = schema._zod.def.type;
681
- if (type === "optional" || type === "nullable" || type === "default") {
682
- const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
683
- if (inner) {
684
- return unwrapSchema(inner);
685
- }
686
- }
687
- return schema;
688
- }
689
- function unwrapSchemaWithRequired(schema) {
690
- if (!schema?._zod?.def) {
691
- return { schema, required: true };
692
- }
693
- const type = schema._zod.def.type;
694
- if (type === "optional" || type === "nullable") {
695
- const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
696
- if (inner) {
697
- const result = unwrapSchemaWithRequired(inner);
698
- return { schema: result.schema, required: false };
699
- }
700
- }
701
- if (type === "default") {
702
- const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
703
- if (inner) {
704
- const result = unwrapSchemaWithRequired(inner);
705
- return { schema: result.schema, required: false };
706
- }
707
- }
708
- return { schema, required: true };
709
- }
710
-
711
- // src/lib/declarative/schema-constraints.ts
712
- function getZodConstraints(schema, path) {
713
- if (!schema) {
714
- return { schemaType: "unknown" };
715
- }
716
- const fieldSchema = getSchemaAtPath(schema, path);
717
- if (!fieldSchema) {
718
- return { schemaType: "unknown" };
719
- }
720
- const def = fieldSchema._zod?.def;
721
- if (!def) {
722
- return { schemaType: "unknown" };
723
- }
724
- const type = def.type;
725
- const checks = def.checks || [];
726
- switch (type) {
727
- case "string":
728
- return {
729
- schemaType: "string",
730
- string: extractStringConstraints(checks)
731
- };
732
- case "number":
733
- return {
734
- schemaType: "number",
735
- number: extractNumberConstraints(checks)
736
- };
737
- case "date":
738
- return {
739
- schemaType: "date",
740
- date: extractDateConstraints(checks)
741
- };
742
- case "array":
743
- return {
744
- schemaType: "array",
745
- array: extractArrayConstraints(checks)
746
- };
747
- case "boolean":
748
- return { schemaType: "boolean" };
749
- case "enum":
750
- return { schemaType: "enum" };
751
- default:
752
- return { schemaType: "unknown" };
753
- }
754
- }
755
- function extractConstraints(checks, handlers) {
756
- const constraints = {};
757
- for (const check of checks) {
758
- const checkDef = check._zod?.def;
759
- if (!checkDef) {
760
- continue;
761
- }
762
- const handler = handlers[checkDef.check];
763
- if (handler) {
764
- handler(constraints, checkDef);
765
- }
766
- }
767
- return constraints;
768
- }
769
- var stringConstraintHandlers = {
770
- min_length: (c, def) => {
771
- c.minLength = def.minimum;
772
- },
773
- max_length: (c, def) => {
774
- c.maxLength = def.maximum;
775
- },
776
- length_equals: (c, def) => {
777
- c.minLength = def.length;
778
- c.maxLength = def.length;
779
- },
780
- string_format: (c, def) => {
781
- if (def.format === "email") {
782
- c.inputType = "email";
783
- } else if (def.format === "url") {
784
- c.inputType = "url";
785
- } else if (def.format === "regex" && def.pattern?.source) {
786
- c.pattern = def.pattern.source;
787
- }
788
- }
789
- };
790
- function extractStringConstraints(checks) {
791
- return extractConstraints(checks, stringConstraintHandlers);
792
- }
793
- var numberConstraintHandlers = {
794
- greater_than: (c, def) => {
795
- c.min = def.value;
796
- },
797
- less_than: (c, def) => {
798
- c.max = def.value;
799
- },
800
- number_format: (c, def) => {
801
- if (def.format === "safeint") {
802
- c.isInteger = true;
803
- c.step = 1;
804
- }
805
- },
806
- multiple_of: (c, def) => {
807
- c.step = def.value;
808
- }
809
- };
810
- function extractNumberConstraints(checks) {
811
- return extractConstraints(checks, numberConstraintHandlers);
812
- }
813
- var dateConstraintHandlers = {
814
- greater_than: (c, def) => {
815
- if (def.value) {
816
- c.min = formatDateToISO(def.value);
817
- }
818
- },
819
- less_than: (c, def) => {
820
- if (def.value) {
821
- c.max = formatDateToISO(def.value);
822
- }
823
- }
824
- };
825
- function extractDateConstraints(checks) {
826
- return extractConstraints(checks, dateConstraintHandlers);
827
- }
828
- var arrayConstraintHandlers = {
829
- min_length: (c, def) => {
830
- c.minItems = def.minimum;
831
- },
832
- max_length: (c, def) => {
833
- c.maxItems = def.maximum;
834
- },
835
- length: (c, def) => {
836
- c.minItems = def.length;
837
- c.maxItems = def.length;
838
- }
839
- };
840
- function extractArrayConstraints(checks) {
841
- return extractConstraints(checks, arrayConstraintHandlers);
842
- }
843
- function getSchemaAtPath(schema, path) {
844
- if (!schema || !path) {
845
- return schema;
846
- }
847
- const parts = path.split(".");
848
- let current = schema;
849
- for (const part of parts) {
850
- current = unwrapSchema(current);
851
- if (!current) {
852
- return void 0;
853
- }
854
- if (/^\d+$/.test(part)) {
855
- if (current._zod?.def?.type === "array") {
856
- current = current._zod.def.element;
857
- }
858
- continue;
859
- }
860
- if (current._zod?.def?.type === "object") {
861
- const shape = current._zod.def.shape;
862
- if (shape && part in shape) {
863
- current = shape[part];
864
- } else {
865
- return void 0;
866
- }
867
- } else {
868
- return void 0;
869
- }
870
- }
871
- return unwrapSchema(current);
872
- }
873
- function formatDateToISO(value) {
874
- let date;
875
- if (value instanceof Date) {
876
- date = value;
877
- } else if (typeof value === "string") {
878
- date = new Date(value);
879
- } else if (typeof value === "number") {
880
- date = new Date(value);
881
- } else {
882
- return "";
883
- }
884
- if (isNaN(date.getTime())) {
885
- return "";
886
- }
887
- const year = date.getFullYear();
888
- const month = String(date.getMonth() + 1).padStart(2, "0");
889
- const day = String(date.getDate()).padStart(2, "0");
890
- return `${year}-${month}-${day}`;
891
- }
892
-
893
- // src/lib/declarative/schema-meta.ts
894
- function getFieldMeta(schema, path) {
895
- if (!schema) {
896
- return { required: false };
897
- }
898
- const result = getSchemaAtPath2(schema, path);
899
- if (!result.schema) {
900
- return { required: false };
901
- }
902
- const fieldSchema = result.schema;
903
- const meta = typeof fieldSchema?.meta === "function" ? fieldSchema.meta() : void 0;
904
- return {
905
- ui: meta?.ui,
906
- required: result.required
907
- };
908
- }
909
- function unwrapToBaseSchema(schema) {
910
- if (!schema?._zod?.def) {
911
- return schema;
912
- }
913
- const type = schema._zod.def.type;
914
- if (type === "effects" || type === "transform" || type === "preprocess") {
915
- const inner = schema._zod.def.inner ?? schema._zod.def.schema;
916
- if (inner) {
917
- return unwrapToBaseSchema(inner);
918
- }
919
- }
920
- if (type === "pipeline") {
921
- const inner = schema._zod.def.in;
922
- if (inner) {
923
- return unwrapToBaseSchema(inner);
924
- }
925
- }
926
- return schema;
927
- }
928
- function getSchemaAtPath2(schema, path) {
929
- if (!schema || !path) {
930
- return { schema, required: true };
931
- }
932
- const parts = path.split(".");
933
- let current = schema;
934
- let isRequired2 = true;
935
- for (const part of parts) {
936
- current = unwrapToBaseSchema(current);
937
- const unwrapped = unwrapSchemaWithRequired(current);
938
- current = unwrapped.schema;
939
- if (!unwrapped.required) {
940
- isRequired2 = false;
941
- }
942
- if (!current) {
943
- return { schema: void 0, required: false };
944
- }
945
- current = unwrapToBaseSchema(current);
946
- if (/^\d+$/.test(part)) {
947
- if (current._zod?.def?.type === "array") {
948
- current = current._zod.def.element;
949
- }
950
- continue;
951
- }
952
- if (current._zod?.def?.type === "object") {
953
- const shape = current._zod.def.shape;
954
- if (shape && part in shape) {
955
- current = shape[part];
956
- } else {
957
- return { schema: void 0, required: false };
958
- }
959
- } else {
960
- return { schema: void 0, required: false };
961
- }
962
- }
963
- current = unwrapToBaseSchema(current);
964
- const finalUnwrap = unwrapSchemaWithRequired(current);
965
- return {
966
- // Возвращаем схему ДО unwrap — на ней can быть мета (.default().meta())
967
- schema: current,
968
- required: isRequired2 && finalUnwrap.required
969
- };
970
- }
971
-
972
- // src/lib/declarative/form-fields/base/base-field.tsx
973
- function useDeclarativeField(name) {
974
- const { form, schema, primitiveArrayIndex, disabled, readOnly } = useDeclarativeForm();
975
- const parentGroup = useFormGroup();
976
- let fullPath;
977
- if (name) {
978
- fullPath = parentGroup ? `${parentGroup.name}.${name}` : name;
979
- } else if (parentGroup) {
980
- fullPath = parentGroup.name;
981
- } else {
982
- throw new Error("Field must have a name prop or be inside Form.Group.List for primitive arrays");
983
- }
984
- const schemaInfo = getFieldMeta(schema, fullPath);
985
- const constraints = getZodConstraints(schema, fullPath);
986
- return {
987
- form,
988
- fullPath,
989
- name: name ?? String(primitiveArrayIndex),
990
- meta: schemaInfo.ui,
991
- required: schemaInfo.required,
992
- formDisabled: disabled ?? false,
993
- formReadOnly: readOnly ?? false,
994
- constraints
995
- };
996
- }
997
-
998
- // src/lib/declarative/form-fields/base/use-resolved-field-props.ts
999
- function useResolvedFieldProps(name, props) {
1000
- const {
1001
- form,
1002
- fullPath,
1003
- meta,
1004
- required: schemaRequired,
1005
- formDisabled,
1006
- formReadOnly,
1007
- constraints
1008
- } = useDeclarativeField(name);
1009
- const i18n = useFormI18n();
1010
- const i18nKey = meta?.i18nKey;
1011
- const resolvedTitle = getLocalizedValue(i18n, i18nKey, "title", meta?.title);
1012
- const resolvedPlaceholder = getLocalizedValue(i18n, i18nKey, "placeholder", meta?.placeholder);
1013
- const resolvedDescription = getLocalizedValue(i18n, i18nKey, "description", meta?.description);
1014
- const autoHint = generateConstraintHint(constraints, i18n?.locale ?? "en");
1015
- const helperText = props.helperText ?? resolvedDescription ?? autoHint;
1016
- const localizedOptions = useLocalizedOptions(meta?.options);
1017
- return {
1018
- form,
1019
- fullPath,
1020
- // Props override i18n override schema meta
1021
- label: props.label ?? resolvedTitle,
1022
- placeholder: props.placeholder ?? resolvedPlaceholder,
1023
- helperText,
1024
- // Tooltip from props or schema meta
1025
- tooltip: props.tooltip ?? meta?.tooltip,
1026
- // Required from schema or prop
1027
- required: props.required ?? schemaRequired,
1028
- // Form-level + local props (local wins)
1029
- disabled: props.disabled ?? formDisabled,
1030
- readOnly: props.readOnly ?? formReadOnly,
1031
- // Constraints for additional component configuration
1032
- constraints,
1033
- // Options with i18n translations
1034
- options: localizedOptions
1035
- };
1036
- }
1037
- function createField(options) {
1038
- const { displayName, render } = options;
1039
- const useFieldState = options.useFieldState ?? (() => ({}));
1040
- function FieldComponent(props) {
1041
- const { name, label, placeholder, helperText, required, disabled, readOnly, tooltip, ...componentProps } = props;
1042
- const { form, fullPath, ...resolvedRest } = useResolvedFieldProps(name, {
1043
- label,
1044
- placeholder,
1045
- helperText,
1046
- required,
1047
- disabled,
1048
- readOnly,
1049
- tooltip
1050
- });
1051
- const resolved = {
1052
- label: resolvedRest.label,
1053
- placeholder: resolvedRest.placeholder,
1054
- helperText: resolvedRest.helperText,
1055
- tooltip: resolvedRest.tooltip,
1056
- required: resolvedRest.required,
1057
- disabled: resolvedRest.disabled,
1058
- readOnly: resolvedRest.readOnly,
1059
- constraints: resolvedRest.constraints,
1060
- options: resolvedRest.options
1061
- };
1062
- const fieldState = useFieldState(componentProps, resolved);
1063
- return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1064
- const errors = field.state.meta.errors;
1065
- const isTouched = field.state.meta.isTouched;
1066
- const hasError = isTouched && hasFieldErrors(errors);
1067
- const errorMessage = hasError ? formatFieldErrors(errors) : "";
1068
- return render({
1069
- field,
1070
- value: field.state.value,
1071
- fullPath,
1072
- resolved,
1073
- hasError,
1074
- errorMessage,
1075
- fieldState,
1076
- componentProps
1077
- });
1078
- } });
1079
- }
1080
- FieldComponent.displayName = displayName;
1081
- return FieldComponent;
1082
- }
1083
- function FieldError({
1084
- hasError,
1085
- errorMessage,
1086
- helperText
1087
- }) {
1088
- if (hasError) {
1089
- return /* @__PURE__ */ jsx(Field.ErrorText, { children: errorMessage });
1090
- }
1091
- if (helperText) {
1092
- return /* @__PURE__ */ jsx(Field.HelperText, { children: helperText });
1093
- }
1094
- return null;
1095
- }
1096
- var FieldWrapper = memo(function FieldWrapper2({
1097
- resolved,
1098
- hasError,
1099
- errorMessage,
1100
- children
1101
- }) {
1102
- return /* @__PURE__ */ jsxs(
1103
- Field.Root,
1104
- {
1105
- invalid: hasError,
1106
- required: resolved.required,
1107
- disabled: resolved.disabled,
1108
- readOnly: resolved.readOnly,
1109
- children: [
1110
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
1111
- children,
1112
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
1113
- ]
1114
- }
1115
- );
1116
- });
1117
- function useDebounce(value, delay = 300) {
1118
- const [debouncedValue, setDebouncedValue] = useState(value);
1119
- useEffect(() => {
1120
- const timer = setTimeout(() => setDebouncedValue(value), delay);
1121
- return () => clearTimeout(timer);
1122
- }, [value, delay]);
1123
- return debouncedValue;
1124
- }
1125
- function useAsyncSearch(options = {}) {
1126
- const { useQuery, debounce = 300, minChars = 1, initialValue = "" } = options;
1127
- const [inputValue, setInputValue] = useState(initialValue);
1128
- const debouncedSearch = useDebounce(inputValue, debounce);
1129
- const shouldQuery = debouncedSearch.length >= minChars;
1130
- const queryResult = useQuery?.(shouldQuery ? debouncedSearch : "");
1131
- const { data, isLoading = false, error } = queryResult ?? {};
1132
- return {
1133
- inputValue,
1134
- setInputValue,
1135
- debouncedSearch,
1136
- shouldQuery,
1137
- isLoading,
1138
- data,
1139
- error
1140
- };
1141
- }
1142
- function SelectionFieldLabel({ label, tooltip, required }) {
1143
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1144
- tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
1145
- /* @__PURE__ */ jsx("span", { children: label }),
1146
- /* @__PURE__ */ jsx(FieldTooltip, { ...tooltip })
1147
- ] }) : label,
1148
- required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
1149
- ] });
1150
- }
1151
- function getOptionLabel(item) {
1152
- return typeof item.label === "string" ? item.label : String(item.value);
1153
- }
1154
- function useGroupedOptions(options) {
1155
- const collection = useMemo(() => {
1156
- const hasGroups = options.some((opt) => opt.group);
1157
- return createListCollection({
1158
- items: options,
1159
- itemToString: getOptionLabel,
1160
- itemToValue: (item) => item.value,
1161
- isItemDisabled: (item) => item.disabled ?? false,
1162
- ...hasGroups && {
1163
- groupBy: (item) => item.group ?? ""
1164
- }
1165
- });
1166
- }, [options]);
1167
- const groups = useMemo(() => {
1168
- const hasGroups = options.some((opt) => opt.group);
1169
- if (!hasGroups) {
1170
- return null;
1171
- }
1172
- const groupMap = /* @__PURE__ */ new Map();
1173
- for (const opt of options) {
1174
- const group = opt.group ?? "";
1175
- const existing = groupMap.get(group) ?? [];
1176
- groupMap.set(group, [...existing, opt]);
1177
- }
1178
- return groupMap;
1179
- }, [options]);
1180
- return { collection, groups };
1181
- }
1182
- var FieldEditable = createField({
1183
- displayName: "FieldEditable",
1184
- render: ({ field, resolved, hasError, errorMessage, componentProps }) => {
1185
- const {
1186
- multiline = false,
1187
- activationMode = "click",
1188
- showControls = false,
1189
- editIcon,
1190
- cancelIcon,
1191
- submitIcon,
1192
- submitOnBlur = true
1193
- } = componentProps;
1194
- const currentValue = field.state.value || "";
1195
- return /* @__PURE__ */ jsxs(
1196
- Field.Root,
1197
- {
1198
- invalid: hasError,
1199
- required: resolved.required,
1200
- disabled: resolved.disabled,
1201
- readOnly: resolved.readOnly,
1202
- children: [
1203
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
1204
- /* @__PURE__ */ jsxs(
1205
- Editable.Root,
1206
- {
1207
- value: currentValue,
1208
- onValueChange: (details) => field.handleChange(details.value),
1209
- disabled: resolved.disabled,
1210
- readOnly: resolved.readOnly,
1211
- placeholder: resolved.placeholder ?? "Click to edit",
1212
- activationMode,
1213
- submitMode: submitOnBlur ? "blur" : "enter",
1214
- children: [
1215
- /* @__PURE__ */ jsx(
1216
- Editable.Preview,
1217
- {
1218
- minH: multiline ? "48px" : void 0,
1219
- alignItems: multiline ? "flex-start" : void 0,
1220
- width: "full"
1221
- }
1222
- ),
1223
- multiline ? /* @__PURE__ */ jsx(Editable.Textarea, {}) : /* @__PURE__ */ jsx(Editable.Input, {}),
1224
- showControls && /* @__PURE__ */ jsxs(Editable.Control, { children: [
1225
- /* @__PURE__ */ jsx(Editable.EditTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "xs", children: editIcon ?? "\u270F\uFE0F" }) }),
1226
- /* @__PURE__ */ jsx(Editable.CancelTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "xs", children: cancelIcon ?? "\u2715" }) }),
1227
- /* @__PURE__ */ jsx(Editable.SubmitTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "xs", children: submitIcon ?? "\u2713" }) })
1228
- ] })
1229
- ]
1230
- }
1231
- ),
1232
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
1233
- ]
1234
- }
1235
- );
1236
- }
1237
- });
1238
- var FieldPassword = createField({
1239
- displayName: "FieldPassword",
1240
- useFieldState: (props) => {
1241
- const [visible, setVisible] = useState(props.defaultVisible ?? false);
1242
- return {
1243
- visible,
1244
- toggle: () => setVisible((v) => !v)
1245
- };
1246
- },
1247
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
1248
- InputGroup,
1249
- {
1250
- endElement: /* @__PURE__ */ jsx(
1251
- IconButton,
1252
- {
1253
- tabIndex: -1,
1254
- me: "-2",
1255
- aspectRatio: "square",
1256
- size: "sm",
1257
- variant: "ghost",
1258
- height: "calc(100% - {spacing.2})",
1259
- "aria-label": "Toggle password visibility",
1260
- disabled: resolved.disabled,
1261
- onPointerDown: (e) => {
1262
- if (resolved.disabled) {
1263
- return;
1264
- }
1265
- if (e.button !== 0) {
1266
- return;
1267
- }
1268
- e.preventDefault();
1269
- fieldState.toggle();
1270
- },
1271
- children: fieldState.visible ? /* @__PURE__ */ jsx(LuEyeOff, {}) : /* @__PURE__ */ jsx(LuEye, {})
1272
- }
1273
- ),
1274
- children: /* @__PURE__ */ jsx(
1275
- Input,
1276
- {
1277
- type: fieldState.visible ? "text" : "password",
1278
- value: field.state.value ?? "",
1279
- onChange: (e) => field.handleChange(e.target.value),
1280
- onBlur: field.handleBlur,
1281
- placeholder: resolved.placeholder,
1282
- maxLength: componentProps.maxLength,
1283
- autoComplete: componentProps.autoComplete,
1284
- "data-field-name": fullPath
1285
- }
1286
- )
1287
- }
1288
- ) })
1289
- });
1290
- var DEFAULT_REQUIREMENTS = ["minLength:8", "uppercase", "lowercase", "number", "special"];
1291
- var REQUIREMENT_LABELS = {
1292
- "minLength:8": "Minimum 8 characters",
1293
- uppercase: "At least one uppercase letter",
1294
- lowercase: "At least one lowercase letter",
1295
- number: "At least one digit",
1296
- special: "At least one special character (!@#$%^&*)"
1297
- };
1298
- function checkRequirement(password, requirement) {
1299
- switch (requirement) {
1300
- case "minLength:8":
1301
- return password.length >= 8;
1302
- case "uppercase":
1303
- return /[A-Z]/.test(password);
1304
- case "lowercase":
1305
- return /[a-z]/.test(password);
1306
- case "number":
1307
- return /[0-9]/.test(password);
1308
- case "special":
1309
- return /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
1310
- default:
1311
- return false;
1312
- }
1313
- }
1314
- function calculateStrength(password, requirements) {
1315
- if (!password) {
1316
- return 0;
1317
- }
1318
- const metCount = requirements.filter((req) => checkRequirement(password, req)).length;
1319
- return Math.round(metCount / requirements.length * 100);
1320
- }
1321
- function getStrengthInfo(strength) {
1322
- if (strength < 25) {
1323
- return { label: "Weak", colorPalette: "red" };
1324
- }
1325
- if (strength < 50) {
1326
- return { label: "Medium", colorPalette: "orange" };
1327
- }
1328
- if (strength < 75) {
1329
- return { label: "Good", colorPalette: "yellow" };
1330
- }
1331
- return { label: "Strong", colorPalette: "green" };
1332
- }
1333
- var FieldPasswordStrength = createField({
1334
- displayName: "FieldPasswordStrength",
1335
- useFieldState: (props) => {
1336
- const [visible, setVisible] = useState(props.defaultVisible ?? false);
1337
- return { visible, toggle: () => setVisible((v) => !v) };
1338
- },
1339
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
1340
- const { requirements = DEFAULT_REQUIREMENTS, showRequirements = true } = componentProps;
1341
- const { visible, toggle } = fieldState;
1342
- const value = field.state.value ?? "";
1343
- const strength = calculateStrength(value, requirements);
1344
- const { label: strengthLabel, colorPalette } = getStrengthInfo(strength);
1345
- return /* @__PURE__ */ jsxs(
1346
- Field.Root,
1347
- {
1348
- invalid: hasError,
1349
- required: resolved.required,
1350
- disabled: resolved.disabled,
1351
- readOnly: resolved.readOnly,
1352
- children: [
1353
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
1354
- /* @__PURE__ */ jsxs(VStack, { gap: 2, align: "stretch", width: "100%", children: [
1355
- /* @__PURE__ */ jsxs(HStack, { children: [
1356
- /* @__PURE__ */ jsx(
1357
- Input,
1358
- {
1359
- type: visible ? "text" : "password",
1360
- value,
1361
- onChange: (e) => field.handleChange(e.target.value),
1362
- onBlur: field.handleBlur,
1363
- placeholder: resolved.placeholder ?? "Enter password",
1364
- "data-field-name": fullPath,
1365
- flex: 1
1366
- }
1367
- ),
1368
- /* @__PURE__ */ jsx(
1369
- IconButton,
1370
- {
1371
- "aria-label": visible ? "Hide password" : "Show password",
1372
- onClick: toggle,
1373
- variant: "ghost",
1374
- size: "sm",
1375
- children: visible ? /* @__PURE__ */ jsx(LuEyeOff, {}) : /* @__PURE__ */ jsx(LuEye, {})
1376
- }
1377
- )
1378
- ] }),
1379
- value && /* @__PURE__ */ jsxs(Box, { children: [
1380
- /* @__PURE__ */ jsxs(HStack, { justify: "space-between", mb: 1, children: [
1381
- /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "Strength" }),
1382
- /* @__PURE__ */ jsx(Text, { fontSize: "xs", fontWeight: "medium", color: `${colorPalette}.600`, children: strengthLabel })
1383
- ] }),
1384
- /* @__PURE__ */ jsx(Progress.Root, { value: strength, colorPalette, size: "xs", children: /* @__PURE__ */ jsx(Progress.Track, { children: /* @__PURE__ */ jsx(Progress.Range, {}) }) })
1385
- ] }),
1386
- showRequirements && value && /* @__PURE__ */ jsx(List.Root, { fontSize: "sm", gap: 1, children: requirements.map((req) => {
1387
- const met = checkRequirement(value, req);
1388
- return /* @__PURE__ */ jsxs(List.Item, { display: "flex", alignItems: "center", gap: 2, children: [
1389
- /* @__PURE__ */ jsx(Box, { color: met ? "green.500" : "gray.400", children: met ? /* @__PURE__ */ jsx(LuCheck, { size: 14 }) : /* @__PURE__ */ jsx(LuX, { size: 14 }) }),
1390
- /* @__PURE__ */ jsx(Text, { color: met ? "fg.default" : "fg.muted", children: REQUIREMENT_LABELS[req] })
1391
- ] }, req);
1392
- }) })
1393
- ] }),
1394
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
1395
- ]
1396
- }
1397
- );
1398
- }
1399
- });
1400
- function ImagePopover({ editor, config, disabled }) {
1401
- const [isOpen, setIsOpen] = useState(false);
1402
- const [uploadState, setUploadState] = useState("idle");
1403
- const [errorMessage, setErrorMessage] = useState(null);
1404
- const [previewUrl, setPreviewUrl] = useState(null);
1405
- const [isDragging, setIsDragging] = useState(false);
1406
- const fileInputRef = useRef(null);
1407
- const maxSize = config.maxSize ?? 10 * 1024 * 1024;
1408
- const acceptTypes = config.acceptTypes ?? ["image/*"];
1409
- const handleUpload = useCallback(
1410
- async (file) => {
1411
- if (!file.type.startsWith("image/")) {
1412
- setErrorMessage("File must be an image");
1413
- setUploadState("error");
1414
- return;
1415
- }
1416
- if (file.size > maxSize) {
1417
- const maxSizeMB = (maxSize / 1024 / 1024).toFixed(0);
1418
- setErrorMessage(`Size file\u0430 \u043D\u0435 must \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0442\u044C ${maxSizeMB}MB`);
1419
- setUploadState("error");
1420
- return;
1421
- }
1422
- const preview = URL.createObjectURL(file);
1423
- setPreviewUrl(preview);
1424
- setUploadState("uploading");
1425
- setErrorMessage(null);
1426
- try {
1427
- const formData = new FormData();
1428
- formData.append("file", file);
1429
- if (config.category) {
1430
- formData.append("category", config.category);
1431
- }
1432
- const response = await fetch(config.endpoint, {
1433
- method: "POST",
1434
- body: formData
1435
- });
1436
- const result = await response.json();
1437
- if (!response.ok) {
1438
- throw new Error(result.error || "Upload error");
1439
- }
1440
- if (result.url) {
1441
- ;
1442
- editor.chain().focus().setImage({ src: result.url }).run();
1443
- handleClose();
1444
- } else {
1445
- throw new Error("Image URL not received");
1446
- }
1447
- } catch (err) {
1448
- setErrorMessage(err instanceof Error ? err.message : "Upload error");
1449
- setUploadState("error");
1450
- } finally {
1451
- if (preview) {
1452
- URL.revokeObjectURL(preview);
1453
- }
1454
- }
1455
- },
1456
- [editor, config, maxSize]
1457
- );
1458
- const handleFileSelect = useCallback(
1459
- (e) => {
1460
- const file = e.target.files?.[0];
1461
- if (file) {
1462
- handleUpload(file);
1463
- }
1464
- e.target.value = "";
1465
- },
1466
- [handleUpload]
1467
- );
1468
- const handleDrop = useCallback(
1469
- (e) => {
1470
- e.preventDefault();
1471
- setIsDragging(false);
1472
- const file = e.dataTransfer.files[0];
1473
- if (file) {
1474
- handleUpload(file);
1475
- }
1476
- },
1477
- [handleUpload]
1478
- );
1479
- const handleClose = useCallback(() => {
1480
- setIsOpen(false);
1481
- setUploadState("idle");
1482
- setErrorMessage(null);
1483
- setPreviewUrl(null);
1484
- setIsDragging(false);
1485
- }, []);
1486
- const handleRetry = useCallback(() => {
1487
- setUploadState("idle");
1488
- setErrorMessage(null);
1489
- setPreviewUrl(null);
1490
- }, []);
1491
- return /* @__PURE__ */ jsxs(Popover.Root, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [
1492
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
1493
- IconButton,
1494
- {
1495
- "aria-label": "Insert image",
1496
- size: "sm",
1497
- variant: "ghost",
1498
- onClick: () => setIsOpen(true),
1499
- disabled,
1500
- children: /* @__PURE__ */ jsx(LuImage, {})
1501
- }
1502
- ) }),
1503
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { children: /* @__PURE__ */ jsxs(Popover.Content, { width: "320px", children: [
1504
- /* @__PURE__ */ jsx(Popover.Arrow, { children: /* @__PURE__ */ jsx(Popover.ArrowTip, {}) }),
1505
- /* @__PURE__ */ jsxs(Popover.Body, { p: 3, children: [
1506
- uploadState === "idle" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
1507
- /* @__PURE__ */ jsx(
1508
- Box,
1509
- {
1510
- p: 6,
1511
- borderWidth: "2px",
1512
- borderStyle: "dashed",
1513
- borderColor: isDragging ? "colorPalette.500" : "border",
1514
- borderRadius: "md",
1515
- bg: isDragging ? "colorPalette.50" : "bg.subtle",
1516
- transition: "all 0.2s",
1517
- cursor: "pointer",
1518
- _hover: { borderColor: "colorPalette.400" },
1519
- onDragOver: (e) => {
1520
- e.preventDefault();
1521
- setIsDragging(true);
1522
- },
1523
- onDragLeave: (e) => {
1524
- e.preventDefault();
1525
- setIsDragging(false);
1526
- },
1527
- onDrop: handleDrop,
1528
- onClick: () => fileInputRef.current?.click(),
1529
- children: /* @__PURE__ */ jsx(Center, { children: /* @__PURE__ */ jsxs(VStack, { gap: 2, children: [
1530
- /* @__PURE__ */ jsx(Icon, { fontSize: "2xl", color: "fg.muted", children: /* @__PURE__ */ jsx(LuUpload, {}) }),
1531
- /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "medium", textAlign: "center", children: "Drag image here" }),
1532
- /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "or click to select" })
1533
- ] }) })
1534
- }
1535
- ),
1536
- /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", textAlign: "center", children: [
1537
- "PNG, JPG, WEBP up to ",
1538
- (maxSize / 1024 / 1024).toFixed(0),
1539
- "MB"
1540
- ] }),
1541
- /* @__PURE__ */ jsx(
1542
- "input",
1543
- {
1544
- ref: fileInputRef,
1545
- type: "file",
1546
- accept: acceptTypes.join(","),
1547
- onChange: handleFileSelect,
1548
- style: { display: "none" }
1549
- }
1550
- ),
1551
- /* @__PURE__ */ jsx(HStack, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }) })
1552
- ] }),
1553
- uploadState === "uploading" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
1554
- previewUrl && /* @__PURE__ */ jsx(Box, { borderRadius: "md", overflow: "hidden", bg: "bg.subtle", children: /* @__PURE__ */ jsx(Image, { src: previewUrl, alt: "Preview", maxH: "150px", w: "100%", objectFit: "contain" }) }),
1555
- /* @__PURE__ */ jsx(Center, { py: 2, children: /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
1556
- /* @__PURE__ */ jsx(Spinner, { size: "sm", color: "colorPalette.500" }),
1557
- /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "fg.muted", children: "Loading..." })
1558
- ] }) })
1559
- ] }),
1560
- uploadState === "error" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
1561
- previewUrl && /* @__PURE__ */ jsxs(Box, { borderRadius: "md", overflow: "hidden", bg: "bg.subtle", position: "relative", children: [
1562
- /* @__PURE__ */ jsx(Image, { src: previewUrl, alt: "Preview", maxH: "150px", w: "100%", objectFit: "contain", opacity: 0.5 }),
1563
- /* @__PURE__ */ jsx(Center, { position: "absolute", inset: 0, bg: "blackAlpha.500", borderRadius: "md", children: /* @__PURE__ */ jsx(Icon, { color: "red.400", fontSize: "2xl", children: /* @__PURE__ */ jsx(LuX, {}) }) })
1564
- ] }),
1565
- /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "red.400", textAlign: "center", children: errorMessage }),
1566
- /* @__PURE__ */ jsxs(HStack, { justify: "center", gap: 2, children: [
1567
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }),
1568
- /* @__PURE__ */ jsx(Button, { size: "sm", colorPalette: "brand", onClick: handleRetry, children: "Try again" })
1569
- ] })
1570
- ] })
1571
- ] })
1572
- ] }) }) })
1573
- ] });
1574
- }
1575
- function LinkPopover({ editor, disabled }) {
1576
- const [url, setUrl] = useState("");
1577
- const [isOpen, setIsOpen] = useState(false);
1578
- const isActive = editor.isActive("link");
1579
- const handleOpen = useCallback(() => {
1580
- if (isActive) {
1581
- editor.chain().focus().unsetLink().run();
1582
- } else {
1583
- const currentUrl = editor.getAttributes("link").href ?? "";
1584
- setUrl(currentUrl);
1585
- setIsOpen(true);
1586
- }
1587
- }, [editor, isActive]);
1588
- const handleClose = useCallback(() => {
1589
- setIsOpen(false);
1590
- setUrl("");
1591
- }, []);
1592
- const handleSubmit = useCallback(() => {
1593
- if (url) {
1594
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
1595
- }
1596
- handleClose();
1597
- }, [editor, url, handleClose]);
1598
- const handleRemove = useCallback(() => {
1599
- editor.chain().focus().unsetLink().run();
1600
- handleClose();
1601
- }, [editor, handleClose]);
1602
- const handleKeyDown = useCallback(
1603
- (e) => {
1604
- if (e.key === "Enter") {
1605
- e.preventDefault();
1606
- handleSubmit();
1607
- } else if (e.key === "Escape") {
1608
- handleClose();
1609
- }
1610
- },
1611
- [handleSubmit, handleClose]
1612
- );
1613
- return /* @__PURE__ */ jsxs(Popover.Root, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [
1614
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
1615
- IconButton,
1616
- {
1617
- "aria-label": isActive ? "Remove \u0441\u0441\u044B\u043B\u043A\u0443" : "Add \u0441\u0441\u044B\u043B\u043A\u0443",
1618
- size: "sm",
1619
- variant: isActive ? "solid" : "ghost",
1620
- colorPalette: isActive ? "brand" : void 0,
1621
- onClick: handleOpen,
1622
- disabled,
1623
- children: isActive ? /* @__PURE__ */ jsx(LuUnlink, {}) : /* @__PURE__ */ jsx(LuLink, {})
1624
- }
1625
- ) }),
1626
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { children: /* @__PURE__ */ jsxs(Popover.Content, { width: "300px", children: [
1627
- /* @__PURE__ */ jsx(Popover.Arrow, { children: /* @__PURE__ */ jsx(Popover.ArrowTip, {}) }),
1628
- /* @__PURE__ */ jsx(Popover.Body, { p: 3, children: /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
1629
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
1630
- Input,
1631
- {
1632
- placeholder: "https://example.com",
1633
- value: url,
1634
- onChange: (e) => setUrl(e.target.value),
1635
- onKeyDown: handleKeyDown,
1636
- size: "sm",
1637
- autoFocus: true
1638
- }
1639
- ) }),
1640
- /* @__PURE__ */ jsxs(HStack, { gap: 2, justify: "flex-end", children: [
1641
- editor.isActive("link") && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", colorPalette: "red", onClick: handleRemove, children: "Remove" }),
1642
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }),
1643
- /* @__PURE__ */ jsx(Button, { size: "sm", colorPalette: "brand", onClick: handleSubmit, disabled: !url.trim(), children: "Apply" })
1644
- ] })
1645
- ] }) })
1646
- ] }) }) })
1647
- ] });
1648
- }
1649
- var DEFAULT_TOOLBAR_BUTTONS = [
1650
- "bold",
1651
- "italic",
1652
- "underline",
1653
- "strike",
1654
- "code",
1655
- "heading1",
1656
- "heading2",
1657
- "heading3",
1658
- "bulletList",
1659
- "orderedList",
1660
- "blockquote",
1661
- "link",
1662
- "undo",
1663
- "redo"
1664
- ];
1665
- var TOOLBAR_CONFIG = {
1666
- bold: {
1667
- icon: /* @__PURE__ */ jsx(LuBold, {}),
1668
- label: "Bold",
1669
- action: (editor) => editor?.chain().focus().toggleBold().run(),
1670
- isActive: (editor) => editor?.isActive("bold") ?? false
1671
- },
1672
- italic: {
1673
- icon: /* @__PURE__ */ jsx(LuItalic, {}),
1674
- label: "Italic",
1675
- action: (editor) => editor?.chain().focus().toggleItalic().run(),
1676
- isActive: (editor) => editor?.isActive("italic") ?? false
1677
- },
1678
- underline: {
1679
- icon: /* @__PURE__ */ jsx(LuUnderline, {}),
1680
- label: "\u041F\u043E\u0434\u0447\u0451\u0440\u043A\u043D\u0443\u0442\u044B\u0439",
1681
- action: (editor) => editor?.chain().focus().toggleUnderline().run(),
1682
- isActive: (editor) => editor?.isActive("underline") ?? false
1683
- },
1684
- strike: {
1685
- icon: /* @__PURE__ */ jsx(LuStrikethrough, {}),
1686
- label: "Strikethrough",
1687
- action: (editor) => editor?.chain().focus().toggleStrike().run(),
1688
- isActive: (editor) => editor?.isActive("strike") ?? false
1689
- },
1690
- code: {
1691
- icon: /* @__PURE__ */ jsx(LuCode, {}),
1692
- label: "\u041A\u043E\u0434",
1693
- action: (editor) => editor?.chain().focus().toggleCode().run(),
1694
- isActive: (editor) => editor?.isActive("code") ?? false
1695
- },
1696
- heading1: {
1697
- icon: /* @__PURE__ */ jsx(LuHeading1, {}),
1698
- label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 1",
1699
- action: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
1700
- isActive: (editor) => editor?.isActive("heading", { level: 1 }) ?? false
1701
- },
1702
- heading2: {
1703
- icon: /* @__PURE__ */ jsx(LuHeading2, {}),
1704
- label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 2",
1705
- action: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
1706
- isActive: (editor) => editor?.isActive("heading", { level: 2 }) ?? false
1707
- },
1708
- heading3: {
1709
- icon: /* @__PURE__ */ jsx(LuHeading3, {}),
1710
- label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 3",
1711
- action: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(),
1712
- isActive: (editor) => editor?.isActive("heading", { level: 3 }) ?? false
1713
- },
1714
- bulletList: {
1715
- icon: /* @__PURE__ */ jsx(LuList, {}),
1716
- label: "Bullet list",
1717
- action: (editor) => editor?.chain().focus().toggleBulletList().run(),
1718
- isActive: (editor) => editor?.isActive("bulletList") ?? false
1719
- },
1720
- orderedList: {
1721
- icon: /* @__PURE__ */ jsx(LuListOrdered, {}),
1722
- label: "Ordered list",
1723
- action: (editor) => editor?.chain().focus().toggleOrderedList().run(),
1724
- isActive: (editor) => editor?.isActive("orderedList") ?? false
1725
- },
1726
- blockquote: {
1727
- icon: /* @__PURE__ */ jsx(LuQuote, {}),
1728
- label: "Quote",
1729
- action: (editor) => editor?.chain().focus().toggleBlockquote().run(),
1730
- isActive: (editor) => editor?.isActive("blockquote") ?? false
1731
- },
1732
- link: {
1733
- icon: /* @__PURE__ */ jsx(LuLink, {}),
1734
- label: "\u0421\u0441\u044B\u043B\u043A\u0430",
1735
- action: (editor) => {
1736
- if (editor?.isActive("link")) {
1737
- editor.chain().focus().unsetLink().run();
1738
- } else {
1739
- const url = window.prompt("URL");
1740
- if (url) {
1741
- editor?.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
1742
- }
1743
- }
1744
- },
1745
- isActive: (editor) => editor?.isActive("link") ?? false
1746
- },
1747
- undo: {
1748
- icon: /* @__PURE__ */ jsx(LuUndo, {}),
1749
- label: "Undo",
1750
- action: (editor) => editor?.chain().focus().undo().run()
1751
- },
1752
- redo: {
1753
- icon: /* @__PURE__ */ jsx(LuRedo, {}),
1754
- label: "Redo",
1755
- action: (editor) => editor?.chain().focus().redo().run()
1756
- },
1757
- // Кнопка image processesся отдельно через ImagePopover (аналогично link)
1758
- image: {
1759
- icon: /* @__PURE__ */ jsx(LuImage, {}),
1760
- label: "Insert image",
1761
- action: () => {
1762
- }
1763
- }
1764
- };
1765
- function safeParseJSON(value) {
1766
- try {
1767
- return JSON.parse(value);
1768
- } catch {
1769
- console.warn("RichText: Invalid JSON content, using empty document");
1770
- return "";
1771
- }
1772
- }
1773
- function FieldRichText({
1774
- name,
1775
- label,
1776
- placeholder,
1777
- helperText,
1778
- required,
1779
- disabled,
1780
- readOnly,
1781
- tooltip,
1782
- minHeight = "150px",
1783
- maxHeight,
1784
- showToolbar = true,
1785
- toolbarButtons = DEFAULT_TOOLBAR_BUTTONS,
1786
- outputFormat = "html",
1787
- imageUpload
1788
- }) {
1789
- const {
1790
- form,
1791
- fullPath,
1792
- label: resolvedLabel,
1793
- placeholder: resolvedPlaceholder,
1794
- helperText: resolvedHelperText,
1795
- tooltip: resolvedTooltip,
1796
- required: resolvedRequired,
1797
- disabled: resolvedDisabled,
1798
- readOnly: resolvedReadOnly
1799
- } = useResolvedFieldProps(name, { label, placeholder, helperText, required, disabled, readOnly, tooltip });
1800
- return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1801
- const { hasError, errorMessage } = getFieldErrors(field);
1802
- return /* @__PURE__ */ jsxs(
1803
- Field.Root,
1804
- {
1805
- invalid: hasError,
1806
- required: resolvedRequired,
1807
- disabled: resolvedDisabled,
1808
- readOnly: resolvedReadOnly,
1809
- children: [
1810
- /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, tooltip: resolvedTooltip, required: resolvedRequired }),
1811
- /* @__PURE__ */ jsx(
1812
- RichTextEditor,
1813
- {
1814
- value: field.state.value,
1815
- onChange: (value) => field.handleChange(value),
1816
- onBlur: field.handleBlur,
1817
- placeholder: resolvedPlaceholder,
1818
- minHeight,
1819
- maxHeight,
1820
- showToolbar,
1821
- toolbarButtons,
1822
- outputFormat,
1823
- disabled,
1824
- readOnly,
1825
- hasError,
1826
- fieldName: fullPath,
1827
- imageUpload
1828
- }
1829
- ),
1830
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolvedHelperText })
1831
- ]
1832
- }
1833
- );
1834
- } });
1835
- }
1836
- function RichTextEditor({
1837
- value,
1838
- onChange,
1839
- onBlur,
1840
- placeholder,
1841
- minHeight,
1842
- maxHeight,
1843
- showToolbar,
1844
- toolbarButtons,
1845
- outputFormat,
1846
- disabled,
1847
- readOnly,
1848
- hasError,
1849
- fieldName,
1850
- imageUpload
1851
- }) {
1852
- const extensions = useMemo(() => {
1853
- const baseExtensions = [
1854
- StarterKit,
1855
- Underline,
1856
- Link.configure({
1857
- openOnClick: false,
1858
- HTMLAttributes: {
1859
- rel: "noopener noreferrer",
1860
- target: "_blank"
1861
- }
1862
- }),
1863
- Placeholder.configure({
1864
- placeholder: placeholder ?? "Start typing..."
1865
- }),
1866
- // Add Image extension only if imageUpload is configured
1867
- ...imageUpload ? [
1868
- TiptapImage.configure({
1869
- inline: false,
1870
- allowBase64: false,
1871
- HTMLAttributes: {
1872
- class: "richtext-image"
1873
- }
1874
- })
1875
- ] : []
1876
- ];
1877
- return baseExtensions;
1878
- }, [placeholder, imageUpload]);
1879
- const editor = useEditor({
1880
- // Cast needed: minor @tiptap/core version drift (e.g. 3.20.0 vs 3.20.1) causes nominal type mismatch
1881
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- @tiptap/core version incompatibility
1882
- extensions,
1883
- content: outputFormat === "json" && value ? safeParseJSON(value) : value || "",
1884
- editable: !disabled && !readOnly,
1885
- onUpdate: ({ editor: editor2 }) => {
1886
- if (outputFormat === "json") {
1887
- onChange(JSON.stringify(editor2.getJSON()));
1888
- } else {
1889
- onChange(editor2.getHTML());
1890
- }
1891
- },
1892
- onBlur: () => {
1893
- onBlur();
1894
- },
1895
- immediatelyRender: false
1896
- });
1897
- useEffect(() => {
1898
- if (!editor) {
1899
- return;
1900
- }
1901
- const currentContent = outputFormat === "json" ? JSON.stringify(editor.getJSON()) : editor.getHTML();
1902
- if (value !== currentContent) {
1903
- const content = outputFormat === "json" && value ? safeParseJSON(value) : value || "";
1904
- editor.commands.setContent(content, { emitUpdate: false });
1905
- }
1906
- }, [editor, value, outputFormat]);
1907
- useEffect(() => {
1908
- if (editor) {
1909
- editor.setEditable(!disabled && !readOnly);
1910
- }
1911
- }, [editor, disabled, readOnly]);
1912
- if (!editor) {
1913
- return null;
1914
- }
1915
- return /* @__PURE__ */ jsxs(
1916
- Box,
1917
- {
1918
- borderWidth: "1px",
1919
- borderRadius: "md",
1920
- borderColor: hasError ? "border.error" : "border",
1921
- overflow: "hidden",
1922
- "data-field-name": fieldName,
1923
- _focusWithin: {
1924
- borderColor: hasError ? "border.error" : "colorPalette.500",
1925
- boxShadow: hasError ? "0 0 0 1px var(--chakra-colors-border-error)" : "0 0 0 1px var(--chakra-colors-colorPalette-500)"
1926
- },
1927
- children: [
1928
- showToolbar && !readOnly && /* @__PURE__ */ jsx(HStack, { p: 1, gap: 0.5, borderBottomWidth: "1px", borderColor: "border", bg: "bg.subtle", flexWrap: "wrap", children: toolbarButtons.map((button) => {
1929
- if (button === "link") {
1930
- return /* @__PURE__ */ jsx(LinkPopover, { editor, disabled }, button);
1931
- }
1932
- if (button === "image") {
1933
- if (!imageUpload) {
1934
- return null;
1935
- }
1936
- return /* @__PURE__ */ jsx(ImagePopover, { editor, config: imageUpload, disabled }, button);
1937
- }
1938
- const config = TOOLBAR_CONFIG[button];
1939
- const isActive = config.isActive?.(editor) ?? false;
1940
- return /* @__PURE__ */ jsx(
1941
- IconButton,
1942
- {
1943
- "aria-label": config.label,
1944
- size: "sm",
1945
- variant: isActive ? "solid" : "ghost",
1946
- colorPalette: isActive ? "brand" : void 0,
1947
- onClick: () => config.action(editor),
1948
- disabled,
1949
- children: config.icon
1950
- },
1951
- button
1952
- );
1953
- }) }),
1954
- /* @__PURE__ */ jsx(
1955
- Box,
1956
- {
1957
- minHeight,
1958
- maxHeight,
1959
- overflowY: maxHeight ? "auto" : void 0,
1960
- p: 3,
1961
- css: {
1962
- "& .tiptap": {
1963
- outline: "none",
1964
- minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight
1965
- },
1966
- "& .tiptap p.is-editor-empty:first-child::before": {
1967
- color: "var(--chakra-colors-fg-muted)",
1968
- content: "attr(data-placeholder)",
1969
- float: "left",
1970
- height: 0,
1971
- pointerEvents: "none"
1972
- },
1973
- "& .tiptap h1": {
1974
- fontSize: "2xl",
1975
- fontWeight: "bold",
1976
- marginTop: "1em",
1977
- marginBottom: "0.5em"
1978
- },
1979
- "& .tiptap h2": {
1980
- fontSize: "xl",
1981
- fontWeight: "bold",
1982
- marginTop: "1em",
1983
- marginBottom: "0.5em"
1984
- },
1985
- "& .tiptap h3": {
1986
- fontSize: "lg",
1987
- fontWeight: "semibold",
1988
- marginTop: "1em",
1989
- marginBottom: "0.5em"
1990
- },
1991
- "& .tiptap ul, & .tiptap ol": {
1992
- paddingLeft: "1.5em",
1993
- marginTop: "0.5em",
1994
- marginBottom: "0.5em"
1995
- },
1996
- "& .tiptap blockquote": {
1997
- borderLeft: "3px solid var(--chakra-colors-border)",
1998
- paddingLeft: "1em",
1999
- marginLeft: 0,
2000
- marginTop: "0.5em",
2001
- marginBottom: "0.5em",
2002
- fontStyle: "italic",
2003
- color: "var(--chakra-colors-fg-muted)"
2004
- },
2005
- "& .tiptap code": {
2006
- backgroundColor: "var(--chakra-colors-bg-subtle)",
2007
- borderRadius: "3px",
2008
- padding: "0.2em 0.4em",
2009
- fontFamily: "mono",
2010
- fontSize: "0.9em"
2011
- },
2012
- "& .tiptap a": {
2013
- color: "var(--chakra-colors-colorPalette-500)",
2014
- textDecoration: "underline",
2015
- cursor: "pointer"
2016
- },
2017
- "& .tiptap p": {
2018
- marginTop: "0.25em",
2019
- marginBottom: "0.25em"
2020
- },
2021
- "& .tiptap img, & .tiptap .richtext-image": {
2022
- maxWidth: "100%",
2023
- height: "auto",
2024
- borderRadius: "4px",
2025
- marginTop: "0.5em",
2026
- marginBottom: "0.5em"
2027
- }
2028
- },
2029
- children: /* @__PURE__ */ jsx(EditorContent, { editor })
2030
- }
2031
- )
2032
- ]
2033
- }
2034
- );
2035
- }
2036
- function getInputModeFromType(type) {
2037
- switch (type) {
2038
- case "email":
2039
- return "email";
2040
- case "tel":
2041
- return "tel";
2042
- case "url":
2043
- return "url";
2044
- default:
2045
- return "text";
2046
- }
2047
- }
2048
- var FieldString = createField({
2049
- displayName: "FieldString",
2050
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2051
- const { constraints } = resolved;
2052
- const type = componentProps.type ?? constraints.string?.inputType ?? "text";
2053
- const maxLength = componentProps.maxLength ?? constraints.string?.maxLength;
2054
- const minLength = componentProps.minLength ?? constraints.string?.minLength;
2055
- const pattern = componentProps.pattern ?? constraints.string?.pattern;
2056
- const inputMode = componentProps.inputMode ?? getInputModeFromType(type);
2057
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
2058
- Input,
2059
- {
2060
- type,
2061
- inputMode,
2062
- value: field.state.value ?? "",
2063
- onChange: (e) => field.handleChange(e.target.value),
2064
- onBlur: field.handleBlur,
2065
- placeholder: resolved.placeholder,
2066
- maxLength,
2067
- minLength,
2068
- pattern,
2069
- autoComplete: componentProps.autoComplete,
2070
- "data-field-name": fullPath
2071
- }
2072
- ) });
2073
- }
2074
- });
2075
- var FieldTextarea = createField({
2076
- displayName: "FieldTextarea",
2077
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2078
- const { constraints } = resolved;
2079
- const maxLength = componentProps.maxLength ?? constraints.string?.maxLength;
2080
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
2081
- Textarea,
2082
- {
2083
- value: field.state.value ?? "",
2084
- onChange: (e) => field.handleChange(e.target.value),
2085
- onBlur: field.handleBlur,
2086
- placeholder: resolved.placeholder,
2087
- rows: componentProps.rows,
2088
- autoresize: componentProps.autoresize,
2089
- resize: componentProps.resize ?? "vertical",
2090
- maxLength,
2091
- "data-field-name": fullPath
2092
- }
2093
- ) });
2094
- }
2095
- });
2096
- var FieldCurrency = createField({
2097
- displayName: "FieldCurrency",
2098
- useFieldState: (props) => {
2099
- const { currency = "RUB", currencyDisplay = "symbol", decimalScale = 2 } = props;
2100
- const formatOptions = useMemo(
2101
- () => ({
2102
- style: "currency",
2103
- currency,
2104
- currencyDisplay,
2105
- minimumFractionDigits: decimalScale,
2106
- maximumFractionDigits: decimalScale
2107
- }),
2108
- [currency, currencyDisplay, decimalScale]
2109
- );
2110
- return { formatOptions };
2111
- },
2112
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
2113
- const value = field.state.value;
2114
- const { min, max, step = 0.01, size } = componentProps;
2115
- const { formatOptions } = fieldState;
2116
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
2117
- NumberInput.Root,
2118
- {
2119
- value: value?.toString() ?? "",
2120
- onValueChange: (details) => {
2121
- const num = details.valueAsNumber;
2122
- field.handleChange(Number.isNaN(num) ? void 0 : num);
2123
- },
2124
- onBlur: field.handleBlur,
2125
- min,
2126
- max,
2127
- step,
2128
- formatOptions,
2129
- clampValueOnBlur: true,
2130
- size,
2131
- children: [
2132
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2133
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2134
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2135
- ] }),
2136
- /* @__PURE__ */ jsx(NumberInput.Input, { placeholder: resolved.placeholder, "data-field-name": fullPath })
2137
- ]
2138
- }
2139
- ) });
2140
- }
2141
- });
2142
- var FieldNumber = createField({
2143
- displayName: "FieldNumber",
2144
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2145
- const value = field.state.value;
2146
- const { constraints } = resolved;
2147
- const min = componentProps.min ?? constraints.number?.min;
2148
- const max = componentProps.max ?? constraints.number?.max;
2149
- const step = componentProps.step ?? constraints.number?.step;
2150
- const isOptional = resolved.required === false;
2151
- const isEmpty = value === void 0 || value === null;
2152
- const shouldApplyMinMax = !isOptional || !isEmpty;
2153
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
2154
- NumberInput.Root,
2155
- {
2156
- value: value?.toString() ?? "",
2157
- onValueChange: (details) => {
2158
- const num = details.valueAsNumber;
2159
- field.handleChange(Number.isNaN(num) ? void 0 : num);
2160
- },
2161
- onBlur: field.handleBlur,
2162
- min: shouldApplyMinMax ? min : void 0,
2163
- max: shouldApplyMinMax ? max : void 0,
2164
- step,
2165
- children: [
2166
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2167
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2168
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2169
- ] }),
2170
- /* @__PURE__ */ jsx(NumberInput.Input, { placeholder: resolved.placeholder, "data-field-name": fullPath, inputMode: "decimal" })
2171
- ]
2172
- }
2173
- ) });
2174
- }
2175
- });
2176
- var FieldNumberInput = createField({
2177
- displayName: "FieldNumberInput",
2178
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2179
- const value = field.state.value;
2180
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
2181
- NumberInput.Root,
2182
- {
2183
- value: value?.toString() ?? "",
2184
- onValueChange: (details) => {
2185
- const num = details.valueAsNumber;
2186
- field.handleChange(Number.isNaN(num) ? void 0 : num);
2187
- },
2188
- onBlur: field.handleBlur,
2189
- min: componentProps.min,
2190
- max: componentProps.max,
2191
- step: componentProps.step,
2192
- formatOptions: componentProps.formatOptions,
2193
- allowMouseWheel: componentProps.allowMouseWheel,
2194
- clampValueOnBlur: componentProps.clampValueOnBlur ?? true,
2195
- spinOnPress: componentProps.spinOnPress ?? true,
2196
- size: componentProps.size,
2197
- children: [
2198
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2199
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2200
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2201
- ] }),
2202
- /* @__PURE__ */ jsx(NumberInput.Input, { placeholder: resolved.placeholder, "data-field-name": fullPath, inputMode: "decimal" })
2203
- ]
2204
- }
2205
- ) });
2206
- }
2207
- });
2208
- var FieldPercentage = createField({
2209
- displayName: "FieldPercentage",
2210
- useFieldState: (props) => {
2211
- const { decimalScale = 0 } = props;
2212
- const formatOptions = useMemo(
2213
- () => ({
2214
- style: "unit",
2215
- unit: "percent",
2216
- unitDisplay: "short",
2217
- minimumFractionDigits: decimalScale,
2218
- maximumFractionDigits: decimalScale
2219
- }),
2220
- [decimalScale]
2221
- );
2222
- return { formatOptions };
2223
- },
2224
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
2225
- const value = field.state.value;
2226
- const { min = 0, max = 100, step = 1, size } = componentProps;
2227
- const { formatOptions } = fieldState;
2228
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
2229
- NumberInput.Root,
2230
- {
2231
- value: value?.toString() ?? "",
2232
- onValueChange: (details) => {
2233
- const num = details.valueAsNumber;
2234
- field.handleChange(Number.isNaN(num) ? void 0 : num);
2235
- },
2236
- onBlur: field.handleBlur,
2237
- min,
2238
- max,
2239
- step,
2240
- formatOptions,
2241
- clampValueOnBlur: true,
2242
- size,
2243
- children: [
2244
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2245
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2246
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2247
- ] }),
2248
- /* @__PURE__ */ jsx(NumberInput.Input, { placeholder: resolved.placeholder, "data-field-name": fullPath })
2249
- ]
2250
- }
2251
- ) });
2252
- }
2253
- });
2254
- var FieldRating = createField({
2255
- displayName: "FieldRating",
2256
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2257
- const { count = 5, allowHalf = false, size = "md", colorPalette, icon, onValueChange } = componentProps;
2258
- const value = field.state.value ?? 0;
2259
- const handleValueChange = (details) => {
2260
- field.handleChange(details.value);
2261
- onValueChange?.(details.value);
2262
- };
2263
- return /* @__PURE__ */ jsxs(
2264
- Field.Root,
2265
- {
2266
- invalid: hasError,
2267
- required: resolved.required,
2268
- disabled: resolved.disabled,
2269
- readOnly: resolved.readOnly,
2270
- children: [
2271
- /* @__PURE__ */ jsxs(
2272
- RatingGroup.Root,
2273
- {
2274
- value,
2275
- onValueChange: handleValueChange,
2276
- count,
2277
- allowHalf,
2278
- size,
2279
- colorPalette,
2280
- disabled: resolved.disabled,
2281
- readOnly: resolved.readOnly,
2282
- "data-field-name": fullPath,
2283
- children: [
2284
- resolved.label && /* @__PURE__ */ jsx(RatingGroup.Label, { children: resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
2285
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
2286
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
2287
- ] }) : resolved.label }),
2288
- /* @__PURE__ */ jsx(RatingGroup.HiddenInput, { onBlur: field.handleBlur }),
2289
- /* @__PURE__ */ jsx(RatingGroup.Control, { children: Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx(RatingGroup.Item, { index: index + 1, children: /* @__PURE__ */ jsx(RatingGroup.ItemIndicator, { icon }) }, index)) })
2290
- ]
2291
- }
2292
- ),
2293
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2294
- ]
2295
- }
2296
- );
2297
- }
2298
- });
2299
- var FieldSlider = createField({
2300
- displayName: "FieldSlider",
2301
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2302
- const { constraints } = resolved;
2303
- const min = componentProps.min ?? constraints.number?.min ?? 0;
2304
- const max = componentProps.max ?? constraints.number?.max ?? 100;
2305
- const step = componentProps.step ?? constraints.number?.step ?? 1;
2306
- const {
2307
- showValue,
2308
- orientation = "horizontal",
2309
- size = "md",
2310
- variant = "outline",
2311
- colorPalette,
2312
- marks,
2313
- origin,
2314
- onValueChange,
2315
- onValueChangeEnd
2316
- } = componentProps;
2317
- const normalizedMarks = marks?.map((mark) => typeof mark === "number" ? { value: mark, label: void 0 } : mark);
2318
- const numValue = field.state.value ?? min;
2319
- const arrayValue = [numValue];
2320
- const handleValueChange = (details) => {
2321
- const newValue = details.value[0] ?? min;
2322
- field.handleChange(newValue);
2323
- onValueChange?.(newValue);
2324
- };
2325
- const handleValueChangeEnd = (details) => {
2326
- const newValue = details.value[0] ?? min;
2327
- onValueChangeEnd?.(newValue);
2328
- };
2329
- return /* @__PURE__ */ jsxs(
2330
- Field.Root,
2331
- {
2332
- invalid: hasError,
2333
- required: resolved.required,
2334
- disabled: resolved.disabled,
2335
- readOnly: resolved.readOnly,
2336
- children: [
2337
- /* @__PURE__ */ jsxs(
2338
- Slider.Root,
2339
- {
2340
- value: arrayValue,
2341
- onValueChange: handleValueChange,
2342
- onValueChangeEnd: handleValueChangeEnd,
2343
- min,
2344
- max,
2345
- step,
2346
- orientation,
2347
- size,
2348
- variant,
2349
- colorPalette,
2350
- origin,
2351
- disabled: resolved.disabled,
2352
- readOnly: resolved.readOnly,
2353
- invalid: hasError,
2354
- thumbAlignment: "center",
2355
- onBlur: field.handleBlur,
2356
- "data-field-name": fullPath,
2357
- children: [
2358
- resolved.label && !showValue && /* @__PURE__ */ jsx(Slider.Label, { children: resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
2359
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
2360
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
2361
- ] }) : resolved.label }),
2362
- resolved.label && showValue && /* @__PURE__ */ jsxs(HStack, { justify: "space-between", children: [
2363
- /* @__PURE__ */ jsx(Slider.Label, { children: resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
2364
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
2365
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
2366
- ] }) : resolved.label }),
2367
- /* @__PURE__ */ jsx(Slider.ValueText, {})
2368
- ] }),
2369
- /* @__PURE__ */ jsxs(Slider.Control, { children: [
2370
- /* @__PURE__ */ jsx(Slider.Track, { children: /* @__PURE__ */ jsx(Slider.Range, {}) }),
2371
- /* @__PURE__ */ jsx(For, { each: arrayValue, children: (_, index) => /* @__PURE__ */ jsx(Slider.Thumb, { index, children: /* @__PURE__ */ jsx(Slider.HiddenInput, {}) }, index) }),
2372
- normalizedMarks && normalizedMarks.length > 0 && /* @__PURE__ */ jsx(Slider.MarkerGroup, { children: normalizedMarks.map((mark, index) => /* @__PURE__ */ jsxs(Slider.Marker, { value: mark.value, children: [
2373
- /* @__PURE__ */ jsx(Slider.MarkerIndicator, {}),
2374
- mark.label
2375
- ] }, index)) })
2376
- ] })
2377
- ]
2378
- }
2379
- ),
2380
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2381
- ]
2382
- }
2383
- );
2384
- }
2385
- });
2386
- var FieldDate = createField({
2387
- displayName: "FieldDate",
2388
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2389
- const { constraints } = resolved;
2390
- const rawValue = field.state.value;
2391
- let stringValue = "";
2392
- if (rawValue instanceof Date) {
2393
- stringValue = rawValue.toISOString().split("T")[0];
2394
- } else if (typeof rawValue === "string") {
2395
- stringValue = rawValue;
2396
- }
2397
- const min = componentProps.min ?? constraints.date?.min;
2398
- const max = componentProps.max ?? constraints.date?.max;
2399
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
2400
- Input,
2401
- {
2402
- type: "date",
2403
- value: stringValue,
2404
- onChange: (e) => field.handleChange(e.target.value),
2405
- onBlur: field.handleBlur,
2406
- placeholder: resolved.placeholder,
2407
- min,
2408
- max,
2409
- "data-field-name": fullPath
2410
- }
2411
- ) });
2412
- }
2413
- });
2414
- function getPresetRange(preset) {
2415
- const today = /* @__PURE__ */ new Date();
2416
- const formatDate2 = (d) => d.toISOString().split("T")[0];
2417
- switch (preset) {
2418
- case "today":
2419
- return { start: formatDate2(today), end: formatDate2(today) };
2420
- case "yesterday": {
2421
- const yesterday = new Date(today);
2422
- yesterday.setDate(today.getDate() - 1);
2423
- return { start: formatDate2(yesterday), end: formatDate2(yesterday) };
2424
- }
2425
- case "thisWeek": {
2426
- const startOfWeek = new Date(today);
2427
- startOfWeek.setDate(today.getDate() - today.getDay() + 1);
2428
- const endOfWeek = new Date(startOfWeek);
2429
- endOfWeek.setDate(startOfWeek.getDate() + 6);
2430
- return { start: formatDate2(startOfWeek), end: formatDate2(endOfWeek) };
2431
- }
2432
- case "lastWeek": {
2433
- const startOfLastWeek = new Date(today);
2434
- startOfLastWeek.setDate(today.getDate() - today.getDay() - 6);
2435
- const endOfLastWeek = new Date(startOfLastWeek);
2436
- endOfLastWeek.setDate(startOfLastWeek.getDate() + 6);
2437
- return { start: formatDate2(startOfLastWeek), end: formatDate2(endOfLastWeek) };
2438
- }
2439
- case "thisMonth": {
2440
- const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
2441
- const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
2442
- return { start: formatDate2(startOfMonth), end: formatDate2(endOfMonth) };
2443
- }
2444
- case "lastMonth": {
2445
- const startOfLastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
2446
- const endOfLastMonth = new Date(today.getFullYear(), today.getMonth(), 0);
2447
- return { start: formatDate2(startOfLastMonth), end: formatDate2(endOfLastMonth) };
2448
- }
2449
- case "thisYear": {
2450
- const startOfYear = new Date(today.getFullYear(), 0, 1);
2451
- const endOfYear = new Date(today.getFullYear(), 11, 31);
2452
- return { start: formatDate2(startOfYear), end: formatDate2(endOfYear) };
2453
- }
2454
- }
2455
- }
2456
- function getPresetLabel(preset) {
2457
- switch (preset) {
2458
- case "today":
2459
- return "Today";
2460
- case "yesterday":
2461
- return "Yesterday";
2462
- case "thisWeek":
2463
- return "This week";
2464
- case "lastWeek":
2465
- return "Last week";
2466
- case "thisMonth":
2467
- return "This month";
2468
- case "lastMonth":
2469
- return "Last month";
2470
- case "thisYear":
2471
- return "This year";
2472
- }
2473
- }
2474
- var FieldDateRange = createField({
2475
- displayName: "FieldDateRange",
2476
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2477
- const {
2478
- startLabel = "Start",
2479
- endLabel = "End",
2480
- startPlaceholder,
2481
- endPlaceholder,
2482
- min,
2483
- max,
2484
- presets,
2485
- orientation = "horizontal",
2486
- size = "md"
2487
- } = componentProps;
2488
- const value = field.state.value ?? { start: "", end: "" };
2489
- const handleStartChange = (newStart) => {
2490
- field.handleChange({ ...value, start: newStart });
2491
- };
2492
- const handleEndChange = (newEnd) => {
2493
- field.handleChange({ ...value, end: newEnd });
2494
- };
2495
- const handlePreset = (preset) => {
2496
- field.handleChange(getPresetRange(preset));
2497
- };
2498
- const Container = orientation === "horizontal" ? HStack : Box;
2499
- return /* @__PURE__ */ jsxs(
2500
- Field.Root,
2501
- {
2502
- invalid: hasError,
2503
- required: resolved.required,
2504
- disabled: resolved.disabled,
2505
- readOnly: resolved.readOnly,
2506
- children: [
2507
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
2508
- /* @__PURE__ */ jsxs(Flex, { gap: 2, direction: orientation === "horizontal" ? "row" : "column", align: "stretch", width: "full", children: [
2509
- /* @__PURE__ */ jsxs(Container, { gap: 2, flex: 1, alignItems: "flex-end", children: [
2510
- /* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsxs(Field.Root, { disabled: resolved.disabled, readOnly: resolved.readOnly, children: [
2511
- /* @__PURE__ */ jsx(Field.Label, { fontSize: "sm", color: "fg.muted", children: startLabel }),
2512
- /* @__PURE__ */ jsx(
2513
- Input,
2514
- {
2515
- type: "date",
2516
- value: value.start,
2517
- onChange: (e) => handleStartChange(e.target.value),
2518
- onBlur: field.handleBlur,
2519
- placeholder: startPlaceholder,
2520
- min,
2521
- max: value.end || max,
2522
- size,
2523
- "data-field-name": `${fullPath}.start`
2524
- }
2525
- )
2526
- ] }) }),
2527
- /* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsxs(Field.Root, { disabled: resolved.disabled, readOnly: resolved.readOnly, children: [
2528
- /* @__PURE__ */ jsx(Field.Label, { fontSize: "sm", color: "fg.muted", children: endLabel }),
2529
- /* @__PURE__ */ jsx(
2530
- Input,
2531
- {
2532
- type: "date",
2533
- value: value.end,
2534
- onChange: (e) => handleEndChange(e.target.value),
2535
- onBlur: field.handleBlur,
2536
- placeholder: endPlaceholder,
2537
- min: value.start || min,
2538
- max,
2539
- size,
2540
- "data-field-name": `${fullPath}.end`
2541
- }
2542
- )
2543
- ] }) })
2544
- ] }),
2545
- presets && presets.length > 0 && !resolved.readOnly && /* @__PURE__ */ jsxs(Menu.Root, { children: [
2546
- /* @__PURE__ */ jsx(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size, disabled: resolved.disabled, children: [
2547
- /* @__PURE__ */ jsx(LuCalendar, {}),
2548
- "Presets",
2549
- /* @__PURE__ */ jsx(LuChevronDown, {})
2550
- ] }) }),
2551
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { children: /* @__PURE__ */ jsx(Menu.Content, { children: presets.map((preset) => /* @__PURE__ */ jsx(Menu.Item, { value: preset, onClick: () => handlePreset(preset), children: getPresetLabel(preset) }, preset)) }) }) })
2552
- ] })
2553
- ] }),
2554
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2555
- ]
2556
- }
2557
- );
2558
- }
2559
- });
2560
- function parseDateTime(value) {
2561
- if (!value) {
2562
- return { date: "", time: "" };
2563
- }
2564
- const match = value.match(/^(\d{4}-\d{2}-\d{2})(?:T(\d{2}:\d{2}))?/);
2565
- if (match) {
2566
- return { date: match[1], time: match[2] || "" };
2567
- }
2568
- return { date: "", time: "" };
2569
- }
2570
- function combineDateTime(date, time) {
2571
- if (!date) {
2572
- return "";
2573
- }
2574
- if (!time) {
2575
- return date;
2576
- }
2577
- return `${date}T${time}:00`;
2578
- }
2579
- var FieldDateTimePicker = createField({
2580
- displayName: "FieldDateTimePicker",
2581
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2582
- const { minDateTime, maxDateTime, timeStep = 15 } = componentProps;
2583
- const minDateTimeStr = minDateTime instanceof Date ? minDateTime.toISOString().slice(0, 16) : minDateTime?.slice(0, 16);
2584
- const maxDateTimeStr = maxDateTime instanceof Date ? maxDateTime.toISOString().slice(0, 16) : maxDateTime?.slice(0, 16);
2585
- const minDate = minDateTimeStr?.slice(0, 10);
2586
- const maxDate = maxDateTimeStr?.slice(0, 10);
2587
- const value = field.state.value;
2588
- const { date, time } = parseDateTime(value);
2589
- const handleDateChange = (newDate) => {
2590
- const combined = combineDateTime(newDate, time);
2591
- field.handleChange(combined || void 0);
2592
- };
2593
- const handleTimeChange = (newTime) => {
2594
- const combined = combineDateTime(date, newTime);
2595
- field.handleChange(combined || void 0);
2596
- };
2597
- return /* @__PURE__ */ jsxs(
2598
- Field.Root,
2599
- {
2600
- invalid: hasError,
2601
- required: resolved.required,
2602
- disabled: resolved.disabled,
2603
- readOnly: resolved.readOnly,
2604
- children: [
2605
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
2606
- /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
2607
- /* @__PURE__ */ jsx(
2608
- Input,
2609
- {
2610
- type: "date",
2611
- value: date,
2612
- onChange: (e) => handleDateChange(e.target.value),
2613
- onBlur: field.handleBlur,
2614
- min: minDate,
2615
- max: maxDate,
2616
- "data-field-name": `${fullPath}-date`,
2617
- flex: 1
2618
- }
2619
- ),
2620
- /* @__PURE__ */ jsx(
2621
- Input,
2622
- {
2623
- type: "time",
2624
- value: time,
2625
- onChange: (e) => handleTimeChange(e.target.value),
2626
- onBlur: field.handleBlur,
2627
- step: timeStep * 60,
2628
- "data-field-name": `${fullPath}-time`,
2629
- width: "150px"
2630
- }
2631
- )
2632
- ] }),
2633
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2634
- ]
2635
- }
2636
- );
2637
- }
2638
- });
2639
- function minutesToHHMM(minutes) {
2640
- return {
2641
- hours: Math.floor(minutes / 60),
2642
- mins: minutes % 60
2643
- };
2644
- }
2645
- function hhmmToMinutes(hours, mins) {
2646
- return hours * 60 + mins;
2647
- }
2648
- var FieldDuration = createField({
2649
- displayName: "FieldDuration",
2650
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
2651
- const { format = "HH:MM", min = 0, max = 1440, step = 15 } = componentProps;
2652
- const value = field.state.value ?? 0;
2653
- const { hours, mins } = minutesToHHMM(value);
2654
- const handleHoursChange = (newHours) => {
2655
- const newValue = hhmmToMinutes(newHours, mins);
2656
- const clampedValue = Math.max(min, Math.min(max, newValue));
2657
- field.handleChange(clampedValue);
2658
- };
2659
- const handleMinsChange = (newMins) => {
2660
- const newValue = hhmmToMinutes(hours, newMins);
2661
- const clampedValue = Math.max(min, Math.min(max, newValue));
2662
- field.handleChange(clampedValue);
2663
- };
2664
- const handleMinutesChange = (newValue) => {
2665
- const clampedValue = Math.max(min, Math.min(max, newValue));
2666
- field.handleChange(clampedValue);
2667
- };
2668
- if (format === "minutes") {
2669
- return /* @__PURE__ */ jsxs(
2670
- Field.Root,
2671
- {
2672
- invalid: hasError,
2673
- required: resolved.required,
2674
- disabled: resolved.disabled,
2675
- readOnly: resolved.readOnly,
2676
- children: [
2677
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
2678
- /* @__PURE__ */ jsxs(
2679
- NumberInput.Root,
2680
- {
2681
- value: value.toString(),
2682
- onValueChange: (details) => {
2683
- const num = details.valueAsNumber;
2684
- if (!Number.isNaN(num)) {
2685
- handleMinutesChange(num);
2686
- }
2687
- },
2688
- onBlur: field.handleBlur,
2689
- min,
2690
- max,
2691
- step,
2692
- children: [
2693
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2694
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2695
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2696
- ] }),
2697
- /* @__PURE__ */ jsx(NumberInput.Input, { placeholder: resolved.placeholder ?? "min", "data-field-name": fullPath })
2698
- ]
2699
- }
2700
- ),
2701
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2702
- ]
2703
- }
2704
- );
2705
- }
2706
- return /* @__PURE__ */ jsxs(
2707
- Field.Root,
2708
- {
2709
- invalid: hasError,
2710
- required: resolved.required,
2711
- disabled: resolved.disabled,
2712
- readOnly: resolved.readOnly,
2713
- children: [
2714
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
2715
- /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
2716
- /* @__PURE__ */ jsxs(
2717
- NumberInput.Root,
2718
- {
2719
- value: hours.toString(),
2720
- onValueChange: (details) => {
2721
- const num = details.valueAsNumber;
2722
- if (!Number.isNaN(num)) {
2723
- handleHoursChange(num);
2724
- }
2725
- },
2726
- onBlur: field.handleBlur,
2727
- min: 0,
2728
- max: Math.floor(max / 60),
2729
- width: "80px",
2730
- children: [
2731
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2732
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2733
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2734
- ] }),
2735
- /* @__PURE__ */ jsx(NumberInput.Input, { "data-field-name": `${fullPath}-hours` })
2736
- ]
2737
- }
2738
- ),
2739
- /* @__PURE__ */ jsx(Text, { fontWeight: "bold", children: ":" }),
2740
- /* @__PURE__ */ jsxs(
2741
- NumberInput.Root,
2742
- {
2743
- value: mins.toString().padStart(2, "0"),
2744
- onValueChange: (details) => {
2745
- const num = details.valueAsNumber;
2746
- if (!Number.isNaN(num)) {
2747
- handleMinsChange(num);
2748
- }
2749
- },
2750
- onBlur: field.handleBlur,
2751
- min: 0,
2752
- max: 59,
2753
- step,
2754
- width: "80px",
2755
- children: [
2756
- /* @__PURE__ */ jsxs(NumberInput.Control, { children: [
2757
- /* @__PURE__ */ jsx(NumberInput.IncrementTrigger, {}),
2758
- /* @__PURE__ */ jsx(NumberInput.DecrementTrigger, {})
2759
- ] }),
2760
- /* @__PURE__ */ jsx(NumberInput.Input, { "data-field-name": `${fullPath}-mins` })
2761
- ]
2762
- }
2763
- )
2764
- ] }),
2765
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
2766
- ]
2767
- }
2768
- );
2769
- }
2770
- });
2771
- var DAYS_OF_WEEK = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
2772
- var DEFAULT_DAY_NAMES = {
2773
- monday: "Monday",
2774
- tuesday: "Tuesday",
2775
- wednesday: "Wednesday",
2776
- thursday: "Thursday",
2777
- friday: "Friday",
2778
- saturday: "Saturday",
2779
- sunday: "Sunday"
2780
- };
2781
- var DEFAULT_WORKING_HOURS = {
2782
- monday: { open: "09:00", close: "18:00" },
2783
- tuesday: { open: "09:00", close: "18:00" },
2784
- wednesday: { open: "09:00", close: "18:00" },
2785
- thursday: { open: "09:00", close: "18:00" },
2786
- friday: { open: "09:00", close: "18:00" },
2787
- saturday: null,
2788
- sunday: null
2789
- };
2790
- var SWITCH_STYLES = {
2791
- /** Switch track width */
2792
- trackWidth: "36px",
2793
- /** Switch track height */
2794
- trackHeight: "20px",
2795
- /** Round indicator (thumb) size */
2796
- thumbSize: "16px",
2797
- /** Thumb offset from edge (2px each side for centering in 20px track) */
2798
- thumbOffset: "2px",
2799
- /** Thumb position in enabled state (trackWidth - thumbSize - thumbOffset = 36 - 16 - 2 = 18) */
2800
- thumbEnabledLeft: "18px"
2801
- };
2802
- function isValidTimeRange(open, close) {
2803
- const [openH, openM] = open.split(":").map(Number);
2804
- const [closeH, closeM] = close.split(":").map(Number);
2805
- const openMinutes = openH * 60 + openM;
2806
- const closeMinutes = closeH * 60 + closeM;
2807
- return closeMinutes > openMinutes;
2808
- }
2809
- var ScheduleContent = memo(function ScheduleContent2({
2810
- field,
2811
- schedule,
2812
- days,
2813
- mergedDayNames,
2814
- showCopyToWeekdays,
2815
- offLabel,
2816
- copyToWeekdaysLabel,
2817
- defaultOpenTime,
2818
- defaultCloseTime,
2819
- disabled,
2820
- readOnly,
2821
- resolvedLabel,
2822
- resolvedHelperText,
2823
- resolvedRequired,
2824
- resolvedTooltip,
2825
- fullPath
2826
- }) {
2827
- const { hasError, errorMessage } = getFieldErrors(field);
2828
- const invalidDays = useMemo(() => {
2829
- const invalid = [];
2830
- for (const day of days) {
2831
- const daySchedule = schedule[day];
2832
- if (daySchedule && !isValidTimeRange(daySchedule.open, daySchedule.close)) {
2833
- invalid.push(day);
2834
- }
2835
- }
2836
- return invalid;
2837
- }, [schedule, days]);
2838
- const handleDayToggle = useCallback(
2839
- (day, enabled) => {
2840
- const newSchedule = {
2841
- ...schedule,
2842
- [day]: enabled ? { open: defaultOpenTime, close: defaultCloseTime } : null
2843
- };
2844
- field.handleChange(newSchedule);
2845
- },
2846
- [schedule, field, defaultOpenTime, defaultCloseTime]
2847
- );
2848
- const handleTimeChange = useCallback(
2849
- (day, timeField, value) => {
2850
- const current = schedule[day];
2851
- if (!current) {
2852
- return;
2853
- }
2854
- const newSchedule = {
2855
- ...schedule,
2856
- [day]: { ...current, [timeField]: value }
2857
- };
2858
- field.handleChange(newSchedule);
2859
- },
2860
- [schedule, field]
2861
- );
2862
- const handleCopyToWeekdays = useCallback(() => {
2863
- const mondaySchedule = schedule.monday;
2864
- if (!mondaySchedule) {
2865
- return;
2866
- }
2867
- const newSchedule = {
2868
- ...schedule,
2869
- monday: mondaySchedule,
2870
- tuesday: mondaySchedule,
2871
- wednesday: mondaySchedule,
2872
- thursday: mondaySchedule,
2873
- friday: mondaySchedule
2874
- };
2875
- field.handleChange(newSchedule);
2876
- }, [schedule, field]);
2877
- return /* @__PURE__ */ jsxs(
2878
- Field.Root,
2879
- {
2880
- invalid: hasError,
2881
- required: resolvedRequired,
2882
- disabled,
2883
- readOnly,
2884
- "data-field-name": fullPath,
2885
- children: [
2886
- /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, tooltip: resolvedTooltip, required: resolvedRequired }),
2887
- /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
2888
- invalidDays.length > 0 && /* @__PURE__ */ jsx(Box, { p: 3, bg: "red.50", borderWidth: "1px", borderColor: "red.200", borderRadius: "md", children: /* @__PURE__ */ jsxs(Text, { color: "red.600", fontSize: "sm", fontWeight: "medium", children: [
2889
- "End time must be after start time: ",
2890
- invalidDays.map((d) => mergedDayNames[d]).join(", ")
2891
- ] }) }),
2892
- showCopyToWeekdays && days.includes("monday") && /* @__PURE__ */ jsxs(HStack, { gap: 2, flexWrap: "wrap", children: [
2893
- /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "fg.muted", children: "Quick actions:" }),
2894
- /* @__PURE__ */ jsx(
2895
- Button,
2896
- {
2897
- type: "button",
2898
- size: "xs",
2899
- variant: "ghost",
2900
- colorPalette: "blue",
2901
- onClick: handleCopyToWeekdays,
2902
- disabled: disabled || readOnly || !schedule.monday,
2903
- children: copyToWeekdaysLabel
2904
- }
2905
- )
2906
- ] }),
2907
- days.map((day) => {
2908
- const daySchedule = schedule[day];
2909
- const isEnabled = daySchedule !== null && daySchedule !== void 0;
2910
- const dayHasError = invalidDays.includes(day);
2911
- return /* @__PURE__ */ jsx(
2912
- Box,
2913
- {
2914
- "data-day": day,
2915
- p: 3,
2916
- bg: dayHasError ? "red.50" : isEnabled ? "bg.panel" : "bg.muted",
2917
- borderRadius: "md",
2918
- borderWidth: dayHasError ? "2px" : "1px",
2919
- borderColor: dayHasError ? "red.300" : "border.muted",
2920
- children: /* @__PURE__ */ jsxs(HStack, { justify: "space-between", flexWrap: "wrap", gap: 3, children: [
2921
- /* @__PURE__ */ jsxs(HStack, { gap: 3, minW: "140px", children: [
2922
- /* @__PURE__ */ jsxs(
2923
- Box,
2924
- {
2925
- as: "label",
2926
- display: "inline-flex",
2927
- alignItems: "center",
2928
- cursor: disabled || readOnly ? "not-allowed" : "pointer",
2929
- position: "relative",
2930
- opacity: disabled || readOnly ? 0.4 : 1,
2931
- children: [
2932
- /* @__PURE__ */ jsx(
2933
- "input",
2934
- {
2935
- type: "checkbox",
2936
- checked: isEnabled,
2937
- onChange: (e) => handleDayToggle(day, e.target.checked),
2938
- disabled: disabled || readOnly,
2939
- "data-switch": day,
2940
- style: {
2941
- position: "absolute",
2942
- opacity: 0,
2943
- width: 0,
2944
- height: 0
2945
- }
2946
- }
2947
- ),
2948
- /* @__PURE__ */ jsx(
2949
- Box,
2950
- {
2951
- w: SWITCH_STYLES.trackWidth,
2952
- h: SWITCH_STYLES.trackHeight,
2953
- bg: isEnabled ? "green.500" : "gray.300",
2954
- borderRadius: "full",
2955
- position: "relative",
2956
- transition: "background 0.2s",
2957
- children: /* @__PURE__ */ jsx(
2958
- Box,
2959
- {
2960
- position: "absolute",
2961
- top: SWITCH_STYLES.thumbOffset,
2962
- left: isEnabled ? SWITCH_STYLES.thumbEnabledLeft : SWITCH_STYLES.thumbOffset,
2963
- w: SWITCH_STYLES.thumbSize,
2964
- h: SWITCH_STYLES.thumbSize,
2965
- bg: "white",
2966
- borderRadius: "full",
2967
- transition: "left 0.2s",
2968
- boxShadow: "sm"
2969
- }
2970
- )
2971
- }
2972
- )
2973
- ]
2974
- }
2975
- ),
2976
- /* @__PURE__ */ jsx(Text, { fontWeight: "medium", color: isEnabled ? "fg" : "fg.muted", children: mergedDayNames[day] })
2977
- ] }),
2978
- isEnabled ? /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
2979
- /* @__PURE__ */ jsx(
2980
- Input,
2981
- {
2982
- type: "time",
2983
- size: "sm",
2984
- width: "120px",
2985
- value: daySchedule?.open || defaultOpenTime,
2986
- onChange: (e) => handleTimeChange(day, "open", e.target.value),
2987
- disabled: disabled || readOnly
2988
- }
2989
- ),
2990
- /* @__PURE__ */ jsx(Text, { color: "fg.muted", children: "\u2014" }),
2991
- /* @__PURE__ */ jsx(
2992
- Input,
2993
- {
2994
- type: "time",
2995
- size: "sm",
2996
- width: "120px",
2997
- value: daySchedule?.close || defaultCloseTime,
2998
- onChange: (e) => handleTimeChange(day, "close", e.target.value),
2999
- disabled: disabled || readOnly
3000
- }
3001
- )
3002
- ] }) : /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "fg.muted", children: offLabel })
3003
- ] })
3004
- },
3005
- day
3006
- );
3007
- })
3008
- ] }),
3009
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolvedHelperText })
3010
- ]
3011
- }
3012
- );
3013
- });
3014
- function FieldSchedule({
3015
- name,
3016
- label,
3017
- helperText,
3018
- required,
3019
- disabled,
3020
- readOnly,
3021
- tooltip,
3022
- dayNames = {},
3023
- defaultSchedule = DEFAULT_WORKING_HOURS,
3024
- days = DAYS_OF_WEEK,
3025
- showCopyToWeekdays = true,
3026
- offLabel = "Day off",
3027
- copyToWeekdaysLabel = "Copy Mon to weekdays",
3028
- defaultOpenTime = "09:00",
3029
- defaultCloseTime = "18:00"
3030
- }) {
3031
- const {
3032
- form,
3033
- fullPath,
3034
- label: resolvedLabel,
3035
- helperText: resolvedHelperText,
3036
- tooltip: resolvedTooltip,
3037
- required: resolvedRequired,
3038
- disabled: resolvedDisabled,
3039
- readOnly: resolvedReadOnly
3040
- } = useResolvedFieldProps(name, { label, helperText, required, disabled, readOnly, tooltip });
3041
- const mergedDayNames = { ...DEFAULT_DAY_NAMES, ...dayNames };
3042
- return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
3043
- const schedule = field.state.value || defaultSchedule;
3044
- return /* @__PURE__ */ jsx(
3045
- ScheduleContent,
3046
- {
3047
- field,
3048
- schedule,
3049
- defaultSchedule,
3050
- days,
3051
- mergedDayNames,
3052
- showCopyToWeekdays,
3053
- offLabel,
3054
- copyToWeekdaysLabel,
3055
- defaultOpenTime,
3056
- defaultCloseTime,
3057
- disabled: resolvedDisabled,
3058
- readOnly: resolvedReadOnly,
3059
- resolvedLabel,
3060
- resolvedHelperText,
3061
- resolvedRequired,
3062
- resolvedTooltip,
3063
- fullPath
3064
- }
3065
- );
3066
- } });
3067
- }
3068
- var FieldTime = createField({
3069
- displayName: "FieldTime",
3070
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
3071
- Input,
3072
- {
3073
- type: "time",
3074
- value: field.state.value ?? "",
3075
- onChange: (e) => field.handleChange(e.target.value),
3076
- onBlur: field.handleBlur,
3077
- placeholder: resolved.placeholder,
3078
- min: componentProps.min,
3079
- max: componentProps.max,
3080
- step: componentProps.step,
3081
- "data-field-name": fullPath
3082
- }
3083
- ) })
3084
- });
3085
- var FieldCheckbox = createField({
3086
- displayName: "FieldCheckbox",
3087
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsxs(
3088
- Field.Root,
3089
- {
3090
- invalid: hasError,
3091
- required: resolved.required,
3092
- disabled: resolved.disabled,
3093
- readOnly: resolved.readOnly,
3094
- children: [
3095
- /* @__PURE__ */ jsxs(
3096
- Checkbox.Root,
3097
- {
3098
- checked: !!field.state.value,
3099
- onCheckedChange: (e) => field.handleChange(!!e.checked),
3100
- colorPalette: componentProps.colorPalette ?? "brand",
3101
- size: componentProps.size ?? "md",
3102
- disabled: resolved.disabled,
3103
- readOnly: resolved.readOnly,
3104
- "data-field-name": fullPath,
3105
- children: [
3106
- /* @__PURE__ */ jsx(Checkbox.HiddenInput, { onBlur: field.handleBlur }),
3107
- /* @__PURE__ */ jsx(Checkbox.Control, {}),
3108
- resolved.label && /* @__PURE__ */ jsx(Checkbox.Label, { children: resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
3109
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
3110
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
3111
- ] }) : resolved.label })
3112
- ]
3113
- }
3114
- ),
3115
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3116
- ]
3117
- }
3118
- )
3119
- });
3120
- var FieldSwitch = createField({
3121
- displayName: "FieldSwitch",
3122
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsxs(
3123
- Field.Root,
3124
- {
3125
- invalid: hasError,
3126
- required: resolved.required,
3127
- disabled: resolved.disabled,
3128
- readOnly: resolved.readOnly,
3129
- children: [
3130
- /* @__PURE__ */ jsxs(
3131
- Switch.Root,
3132
- {
3133
- checked: !!field.state.value,
3134
- onCheckedChange: (e) => field.handleChange(e.checked),
3135
- colorPalette: componentProps.colorPalette ?? "brand",
3136
- size: componentProps.size ?? "md",
3137
- disabled: resolved.disabled,
3138
- readOnly: resolved.readOnly,
3139
- "data-field-name": fullPath,
3140
- children: [
3141
- /* @__PURE__ */ jsx(Switch.HiddenInput, { onBlur: field.handleBlur }),
3142
- /* @__PURE__ */ jsx(Switch.Control, { children: /* @__PURE__ */ jsx(Switch.Thumb, {}) }),
3143
- resolved.label && /* @__PURE__ */ jsx(Switch.Label, { children: resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
3144
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
3145
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
3146
- ] }) : resolved.label })
3147
- ]
3148
- }
3149
- ),
3150
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3151
- ]
3152
- }
3153
- )
3154
- });
3155
- var FieldAutocomplete = createField({
3156
- displayName: "FieldAutocomplete",
3157
- useFieldState: (componentProps) => {
3158
- const {
3159
- inputValue,
3160
- setInputValue,
3161
- isLoading,
3162
- data: queryData
3163
- } = useAsyncSearch({
3164
- useQuery: componentProps.useQuery,
3165
- debounce: componentProps.debounce ?? 300,
3166
- minChars: componentProps.minChars ?? 1
3167
- });
3168
- const { contains } = useFilter({ sensitivity: "base" });
3169
- const suggestions = useMemo(() => {
3170
- if (componentProps.suggestions) {
3171
- const filtered = inputValue ? componentProps.suggestions.filter((s) => contains(s, inputValue)) : componentProps.suggestions.slice(0, 10);
3172
- return filtered.map((s) => ({ label: s, value: s }));
3173
- }
3174
- if (queryData && componentProps.getLabel) {
3175
- const getLabel = componentProps.getLabel;
3176
- return queryData.map((item) => {
3177
- const itemLabel = getLabel(item);
3178
- return { label: itemLabel, value: itemLabel };
3179
- });
3180
- }
3181
- return [];
3182
- }, [componentProps.suggestions, queryData, componentProps.getLabel, inputValue, contains]);
3183
- const collection = useMemo(() => {
3184
- return createListCollection({
3185
- items: suggestions,
3186
- itemToString: (item) => item.label,
3187
- itemToValue: (item) => item.value
3188
- });
3189
- }, [suggestions]);
3190
- return {
3191
- inputValue,
3192
- setInputValue,
3193
- isLoading,
3194
- suggestions,
3195
- collection
3196
- };
3197
- },
3198
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3199
- const currentValue = field.state.value ?? "";
3200
- const minChars = componentProps.minChars ?? 1;
3201
- return /* @__PURE__ */ jsxs(
3202
- Field.Root,
3203
- {
3204
- invalid: hasError,
3205
- required: resolved.required,
3206
- disabled: resolved.disabled,
3207
- readOnly: resolved.readOnly,
3208
- children: [
3209
- /* @__PURE__ */ jsxs(
3210
- Combobox.Root,
3211
- {
3212
- collection: fieldState.collection,
3213
- size: componentProps.size ?? "md",
3214
- variant: componentProps.variant ?? "outline",
3215
- value: currentValue ? [currentValue] : [],
3216
- inputValue: fieldState.inputValue,
3217
- onInputValueChange: (details) => {
3218
- fieldState.setInputValue(details.inputValue);
3219
- field.handleChange(details.inputValue);
3220
- },
3221
- onValueChange: (details) => {
3222
- const newValue = details.value[0] ?? "";
3223
- fieldState.setInputValue(newValue);
3224
- field.handleChange(newValue);
3225
- },
3226
- onInteractOutside: () => field.handleBlur(),
3227
- disabled: resolved.disabled,
3228
- allowCustomValue: true,
3229
- openOnClick: true,
3230
- "data-field-name": fullPath,
3231
- children: [
3232
- resolved.label && /* @__PURE__ */ jsx(Combobox.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3233
- /* @__PURE__ */ jsxs(Combobox.Control, { children: [
3234
- /* @__PURE__ */ jsx(Combobox.Input, { placeholder: resolved.placeholder ?? "Start typing..." }),
3235
- /* @__PURE__ */ jsxs(Combobox.IndicatorGroup, { children: [
3236
- fieldState.isLoading && /* @__PURE__ */ jsx(Spinner, { size: "xs" }),
3237
- /* @__PURE__ */ jsx(Combobox.Trigger, {})
3238
- ] })
3239
- ] }),
3240
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, { children: /* @__PURE__ */ jsxs(Combobox.Content, { children: [
3241
- fieldState.isLoading && fieldState.suggestions.length === 0 && /* @__PURE__ */ jsx(Combobox.Empty, { children: componentProps.loadingMessage ?? "Loading..." }),
3242
- !fieldState.isLoading && fieldState.suggestions.length === 0 && fieldState.inputValue.length >= minChars && /* @__PURE__ */ jsx(Combobox.Empty, { children: componentProps.emptyMessage ?? "No suggestions" }),
3243
- !fieldState.isLoading && fieldState.suggestions.length === 0 && fieldState.inputValue.length < minChars && fieldState.inputValue.length > 0 && /* @__PURE__ */ jsxs(Combobox.Empty, { children: [
3244
- "Enter at least ",
3245
- minChars,
3246
- " characters"
3247
- ] }),
3248
- fieldState.suggestions.map((item) => /* @__PURE__ */ jsxs(Combobox.Item, { item, children: [
3249
- /* @__PURE__ */ jsx(Combobox.ItemText, { children: item.label }),
3250
- /* @__PURE__ */ jsx(Combobox.ItemIndicator, {})
3251
- ] }, item.value))
3252
- ] }) }) })
3253
- ]
3254
- }
3255
- ),
3256
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3257
- ]
3258
- }
3259
- );
3260
- }
3261
- });
3262
- var FieldCheckboxCard = createField({
3263
- displayName: "FieldCheckboxCard",
3264
- render: ({ field, resolved, hasError, errorMessage, componentProps }) => {
3265
- const currentValue = field.state.value;
3266
- const valueArray = currentValue ?? [];
3267
- return /* @__PURE__ */ jsxs(Fieldset.Root, { invalid: hasError, disabled: resolved.disabled, children: [
3268
- /* @__PURE__ */ jsxs(
3269
- CheckboxGroup,
3270
- {
3271
- value: valueArray,
3272
- onValueChange: (value) => field.handleChange(value),
3273
- disabled: resolved.disabled,
3274
- invalid: hasError,
3275
- children: [
3276
- resolved.label && /* @__PURE__ */ jsx(Fieldset.Legend, { mb: 2, children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3277
- /* @__PURE__ */ jsx(
3278
- Flex,
3279
- {
3280
- gap: componentProps.gap ?? 2,
3281
- direction: (componentProps.orientation ?? "horizontal") === "vertical" ? "column" : "row",
3282
- wrap: (componentProps.orientation ?? "horizontal") === "horizontal" ? "wrap" : void 0,
3283
- children: componentProps.options.map((opt) => /* @__PURE__ */ jsxs(
3284
- CheckboxCard.Root,
3285
- {
3286
- value: opt.value,
3287
- size: componentProps.size ?? "md",
3288
- variant: componentProps.variant ?? "outline",
3289
- colorPalette: componentProps.colorPalette,
3290
- align: componentProps.align ?? "start",
3291
- disabled: opt.disabled,
3292
- children: [
3293
- /* @__PURE__ */ jsx(CheckboxCard.HiddenInput, {}),
3294
- /* @__PURE__ */ jsxs(CheckboxCard.Control, { children: [
3295
- /* @__PURE__ */ jsxs(CheckboxCard.Content, { children: [
3296
- opt.icon,
3297
- /* @__PURE__ */ jsx(CheckboxCard.Label, { children: opt.label }),
3298
- opt.description && /* @__PURE__ */ jsx(CheckboxCard.Description, { children: opt.description })
3299
- ] }),
3300
- /* @__PURE__ */ jsx(CheckboxCard.Indicator, {})
3301
- ] })
3302
- ]
3303
- },
3304
- opt.value
3305
- ))
3306
- }
3307
- )
3308
- ]
3309
- }
3310
- ),
3311
- hasError ? /* @__PURE__ */ jsx(Fieldset.ErrorText, { children: errorMessage }) : resolved.helperText && /* @__PURE__ */ jsx(Fieldset.HelperText, { children: resolved.helperText })
3312
- ] });
3313
- }
3314
- });
3315
- var FieldCombobox = createField({
3316
- displayName: "FieldCombobox",
3317
- useFieldState: (componentProps, resolved) => {
3318
- const {
3319
- inputValue,
3320
- setInputValue,
3321
- isLoading,
3322
- data: queryData
3323
- } = useAsyncSearch({
3324
- useQuery: componentProps.useQuery,
3325
- debounce: componentProps.debounce ?? 300,
3326
- minChars: componentProps.minChars ?? 1
3327
- });
3328
- const { contains } = useFilter({ sensitivity: "base" });
3329
- const options = useMemo(() => {
3330
- if (componentProps.options) {
3331
- if (!inputValue) {
3332
- return componentProps.options;
3333
- }
3334
- return componentProps.options.filter((opt) => {
3335
- return contains(getOptionLabel(opt), inputValue);
3336
- });
3337
- }
3338
- if (queryData && componentProps.getLabel && componentProps.getValue) {
3339
- const getLabel = componentProps.getLabel;
3340
- const getValue = componentProps.getValue;
3341
- return queryData.map((item) => ({
3342
- label: getLabel(item),
3343
- value: getValue(item),
3344
- group: componentProps.getGroup?.(item),
3345
- disabled: componentProps.getDisabled?.(item)
3346
- }));
3347
- }
3348
- return [];
3349
- }, [
3350
- componentProps.options,
3351
- queryData,
3352
- componentProps.getLabel,
3353
- componentProps.getValue,
3354
- componentProps.getGroup,
3355
- componentProps.getDisabled,
3356
- inputValue,
3357
- contains
3358
- ]);
3359
- const { collection, groups } = useGroupedOptions(options);
3360
- const resolvedClearable = componentProps.clearable ?? !resolved.required;
3361
- return {
3362
- inputValue,
3363
- setInputValue,
3364
- isLoading,
3365
- options,
3366
- collection,
3367
- groups,
3368
- resolvedClearable
3369
- };
3370
- },
3371
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3372
- const currentValue = field.state.value;
3373
- const minChars = componentProps.minChars ?? 1;
3374
- return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, required: resolved.required, disabled: resolved.disabled, children: [
3375
- /* @__PURE__ */ jsxs(
3376
- Combobox.Root,
3377
- {
3378
- collection: fieldState.collection,
3379
- size: componentProps.size ?? "md",
3380
- variant: componentProps.variant ?? "outline",
3381
- value: currentValue ? [currentValue] : [],
3382
- inputValue: fieldState.inputValue,
3383
- onInputValueChange: (details) => fieldState.setInputValue(details.inputValue),
3384
- onValueChange: (details) => {
3385
- const newValue = details.value[0];
3386
- field.handleChange(newValue ?? "");
3387
- },
3388
- onInteractOutside: () => field.handleBlur(),
3389
- disabled: resolved.disabled,
3390
- allowCustomValue: componentProps.allowCustomValue ?? false,
3391
- openOnClick: true,
3392
- "data-field-name": fullPath,
3393
- children: [
3394
- resolved.label && /* @__PURE__ */ jsx(Combobox.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3395
- /* @__PURE__ */ jsxs(Combobox.Control, { children: [
3396
- /* @__PURE__ */ jsx(Combobox.Input, { placeholder: resolved.placeholder ?? "Search..." }),
3397
- /* @__PURE__ */ jsxs(Combobox.IndicatorGroup, { children: [
3398
- fieldState.isLoading && /* @__PURE__ */ jsx(Spinner, { size: "xs" }),
3399
- fieldState.resolvedClearable && /* @__PURE__ */ jsx(Combobox.ClearTrigger, {}),
3400
- /* @__PURE__ */ jsx(Combobox.Trigger, {})
3401
- ] })
3402
- ] }),
3403
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, { children: /* @__PURE__ */ jsxs(Combobox.Content, { children: [
3404
- fieldState.isLoading && fieldState.options.length === 0 && /* @__PURE__ */ jsx(Combobox.Empty, { children: componentProps.loadingMessage ?? "Loading..." }),
3405
- !fieldState.isLoading && fieldState.options.length === 0 && fieldState.inputValue.length >= minChars && /* @__PURE__ */ jsx(Combobox.Empty, { children: componentProps.emptyMessage ?? "Nothing found" }),
3406
- !fieldState.isLoading && fieldState.options.length === 0 && fieldState.inputValue.length < minChars && fieldState.inputValue.length > 0 && /* @__PURE__ */ jsxs(Combobox.Empty, { children: [
3407
- "Enter at least ",
3408
- minChars,
3409
- " characters"
3410
- ] }),
3411
- fieldState.groups ? Array.from(fieldState.groups.entries()).map(([groupName, groupOptions]) => /* @__PURE__ */ jsxs(Combobox.ItemGroup, { children: [
3412
- groupName && /* @__PURE__ */ jsx(Combobox.ItemGroupLabel, { children: groupName }),
3413
- groupOptions.map((opt) => /* @__PURE__ */ jsxs(Combobox.Item, { item: opt, children: [
3414
- /* @__PURE__ */ jsx(Combobox.ItemText, { children: getOptionLabel(opt) }),
3415
- /* @__PURE__ */ jsx(Combobox.ItemIndicator, {})
3416
- ] }, opt.value))
3417
- ] }, groupName)) : (
3418
- /* Flat options */
3419
- fieldState.options.map((opt) => /* @__PURE__ */ jsxs(Combobox.Item, { item: opt, children: [
3420
- /* @__PURE__ */ jsx(Combobox.ItemText, { children: getOptionLabel(opt) }),
3421
- /* @__PURE__ */ jsx(Combobox.ItemIndicator, {})
3422
- ] }, opt.value))
3423
- )
3424
- ] }) }) })
3425
- ]
3426
- }
3427
- ),
3428
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3429
- ] });
3430
- }
3431
- });
3432
- var FieldListbox = createField({
3433
- displayName: "FieldListbox",
3434
- useFieldState: (componentProps) => {
3435
- return useGroupedOptions(componentProps.options);
3436
- },
3437
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3438
- const currentValue = field.state.value;
3439
- const valueArray = Array.isArray(currentValue) ? currentValue : currentValue ? [currentValue] : [];
3440
- const selectionMode = componentProps.selectionMode ?? "single";
3441
- return /* @__PURE__ */ jsxs(
3442
- Field.Root,
3443
- {
3444
- invalid: hasError,
3445
- required: resolved.required,
3446
- disabled: resolved.disabled,
3447
- readOnly: resolved.readOnly,
3448
- children: [
3449
- /* @__PURE__ */ jsxs(
3450
- Listbox.Root,
3451
- {
3452
- collection: fieldState.collection,
3453
- selectionMode,
3454
- orientation: componentProps.orientation ?? "vertical",
3455
- variant: componentProps.variant ?? "subtle",
3456
- colorPalette: componentProps.colorPalette,
3457
- value: valueArray,
3458
- onValueChange: (details) => {
3459
- if (selectionMode === "single") {
3460
- const newValue = details.value[0];
3461
- field.handleChange(newValue ?? "");
3462
- } else {
3463
- field.handleChange(details.value);
3464
- }
3465
- },
3466
- disabled: resolved.disabled,
3467
- "data-field-name": fullPath,
3468
- children: [
3469
- resolved.label && /* @__PURE__ */ jsx(Listbox.Label, { fontSize: componentProps.size ?? "md", children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3470
- /* @__PURE__ */ jsx(Listbox.Content, { maxH: componentProps.maxHeight, children: fieldState.groups ? (
3471
- /* Grouped options */
3472
- Array.from(fieldState.groups.entries()).map(([groupName, groupOptions]) => /* @__PURE__ */ jsxs(Listbox.ItemGroup, { children: [
3473
- groupName && /* @__PURE__ */ jsx(Listbox.ItemGroupLabel, { children: groupName }),
3474
- groupOptions.map((opt) => /* @__PURE__ */ jsxs(Listbox.Item, { item: opt, children: [
3475
- /* @__PURE__ */ jsx(Listbox.ItemText, { children: getOptionLabel(opt) }),
3476
- /* @__PURE__ */ jsx(Listbox.ItemIndicator, {})
3477
- ] }, opt.value))
3478
- ] }, groupName))
3479
- ) : (
3480
- /* Flat options */
3481
- componentProps.options.map((opt) => /* @__PURE__ */ jsxs(Listbox.Item, { item: opt, children: [
3482
- /* @__PURE__ */ jsx(Listbox.ItemText, { children: getOptionLabel(opt) }),
3483
- /* @__PURE__ */ jsx(Listbox.ItemIndicator, {})
3484
- ] }, opt.value))
3485
- ) })
3486
- ]
3487
- }
3488
- ),
3489
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3490
- ]
3491
- }
3492
- );
3493
- }
3494
- });
3495
- var FieldNativeSelect = createField({
3496
- displayName: "FieldNativeSelect",
3497
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(NativeSelect.Root, { children: [
3498
- /* @__PURE__ */ jsxs(
3499
- NativeSelect.Field,
3500
- {
3501
- value: field.state.value ?? "",
3502
- onChange: (e) => field.handleChange(e.target.value),
3503
- onBlur: field.handleBlur,
3504
- "data-field-name": fullPath,
3505
- children: [
3506
- resolved.placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: resolved.placeholder }),
3507
- componentProps.options.map((opt, idx) => /* @__PURE__ */ jsx("option", { value: opt.value, children: typeof opt.title === "string" ? opt.title : opt.value }, idx))
3508
- ]
3509
- }
3510
- ),
3511
- /* @__PURE__ */ jsx(NativeSelect.Indicator, {})
3512
- ] }) })
3513
- });
3514
- var FieldRadioCard = createField({
3515
- displayName: "FieldRadioCard",
3516
- useFieldState: (componentProps) => {
3517
- const enabledOptions = componentProps.options.filter((opt) => !opt.disabled);
3518
- const handleKeyDown = useCallback(
3519
- (e, currentValue, handleChange) => {
3520
- if (!componentProps.keyboardNavigation || enabledOptions.length === 0) {
3521
- return;
3522
- }
3523
- const isHorizontal = (componentProps.orientation ?? "horizontal") === "horizontal";
3524
- const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp";
3525
- const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown";
3526
- if (e.key !== prevKey && e.key !== nextKey) {
3527
- return;
3528
- }
3529
- e.preventDefault();
3530
- const currentIndex = currentValue ? enabledOptions.findIndex((opt) => opt.value === currentValue) : -1;
3531
- let newIndex;
3532
- if (e.key === nextKey) {
3533
- newIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % enabledOptions.length;
3534
- } else {
3535
- newIndex = currentIndex === -1 ? enabledOptions.length - 1 : (currentIndex - 1 + enabledOptions.length) % enabledOptions.length;
3536
- }
3537
- handleChange(enabledOptions[newIndex].value);
3538
- },
3539
- [componentProps.keyboardNavigation, enabledOptions, componentProps.orientation]
3540
- );
3541
- return { enabledOptions, handleKeyDown };
3542
- },
3543
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3544
- const currentValue = field.state.value;
3545
- return /* @__PURE__ */ jsxs(
3546
- Field.Root,
3547
- {
3548
- invalid: hasError,
3549
- required: resolved.required,
3550
- disabled: resolved.disabled,
3551
- readOnly: resolved.readOnly,
3552
- children: [
3553
- /* @__PURE__ */ jsxs(
3554
- RadioCard.Root,
3555
- {
3556
- value: currentValue ?? "",
3557
- onValueChange: (details) => field.handleChange(details.value),
3558
- onKeyDown: componentProps.keyboardNavigation ? (e) => fieldState.handleKeyDown(e, currentValue, field.handleChange) : void 0,
3559
- disabled: resolved.disabled,
3560
- name: fullPath,
3561
- size: componentProps.size ?? "md",
3562
- variant: componentProps.variant ?? "outline",
3563
- colorPalette: componentProps.colorPalette,
3564
- align: componentProps.align ?? "start",
3565
- orientation: componentProps.orientation ?? "horizontal",
3566
- gap: componentProps.gap ?? 2,
3567
- children: [
3568
- resolved.label && /* @__PURE__ */ jsx(RadioCard.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3569
- componentProps.options.map((opt) => /* @__PURE__ */ jsxs(RadioCard.Item, { value: opt.value, disabled: opt.disabled, children: [
3570
- /* @__PURE__ */ jsx(RadioCard.ItemHiddenInput, {}),
3571
- /* @__PURE__ */ jsxs(RadioCard.ItemControl, { children: [
3572
- /* @__PURE__ */ jsxs(RadioCard.ItemContent, { children: [
3573
- opt.icon,
3574
- /* @__PURE__ */ jsx(RadioCard.ItemText, { children: opt.label }),
3575
- opt.description && /* @__PURE__ */ jsx(RadioCard.ItemDescription, { children: opt.description })
3576
- ] }),
3577
- /* @__PURE__ */ jsx(RadioCard.ItemIndicator, {})
3578
- ] })
3579
- ] }, opt.value))
3580
- ]
3581
- }
3582
- ),
3583
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3584
- ]
3585
- }
3586
- );
3587
- }
3588
- });
3589
- var FieldRadioGroup = createField({
3590
- displayName: "FieldRadioGroup",
3591
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsxs(
3592
- Field.Root,
3593
- {
3594
- invalid: hasError,
3595
- required: resolved.required,
3596
- disabled: resolved.disabled,
3597
- readOnly: resolved.readOnly,
3598
- children: [
3599
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
3600
- /* @__PURE__ */ jsx(
3601
- RadioGroup.Root,
3602
- {
3603
- value: field.state.value ?? void 0,
3604
- onValueChange: (details) => field.handleChange(details.value),
3605
- orientation: componentProps.orientation ?? "vertical",
3606
- size: componentProps.size ?? "md",
3607
- variant: componentProps.variant ?? "solid",
3608
- colorPalette: componentProps.colorPalette ?? "brand",
3609
- disabled: resolved.disabled,
3610
- readOnly: resolved.readOnly,
3611
- "data-field-name": fullPath,
3612
- display: "flex",
3613
- flexDirection: componentProps.orientation === "horizontal" ? "row" : "column",
3614
- gap: componentProps.orientation === "horizontal" ? 4 : 2,
3615
- flexWrap: "wrap",
3616
- children: componentProps.options.map((opt) => /* @__PURE__ */ jsxs(RadioGroup.Item, { value: opt.value, disabled: opt.disabled, children: [
3617
- /* @__PURE__ */ jsx(RadioGroup.ItemHiddenInput, { onBlur: field.handleBlur }),
3618
- /* @__PURE__ */ jsx(RadioGroup.ItemIndicator, {}),
3619
- /* @__PURE__ */ jsx(RadioGroup.ItemText, { children: getOptionLabel(opt) })
3620
- ] }, opt.value))
3621
- }
3622
- ),
3623
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3624
- ]
3625
- }
3626
- )
3627
- });
3628
- var FieldSegmentedGroup = createField({
3629
- displayName: "FieldSegmentedGroup",
3630
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => /* @__PURE__ */ jsxs(
3631
- Field.Root,
3632
- {
3633
- invalid: hasError,
3634
- required: resolved.required,
3635
- disabled: resolved.disabled,
3636
- readOnly: resolved.readOnly,
3637
- children: [
3638
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
3639
- /* @__PURE__ */ jsxs(
3640
- SegmentGroup.Root,
3641
- {
3642
- value: field.state.value ?? "",
3643
- onValueChange: (details) => field.handleChange(details.value),
3644
- disabled: resolved.disabled,
3645
- name: fullPath,
3646
- size: componentProps.size ?? "md",
3647
- orientation: componentProps.orientation ?? "horizontal",
3648
- colorPalette: componentProps.colorPalette,
3649
- children: [
3650
- /* @__PURE__ */ jsx(SegmentGroup.Indicator, {}),
3651
- componentProps.options.map((opt) => /* @__PURE__ */ jsxs(SegmentGroup.Item, { value: opt.value, disabled: opt.disabled, children: [
3652
- /* @__PURE__ */ jsx(SegmentGroup.ItemText, { children: opt.label }),
3653
- /* @__PURE__ */ jsx(SegmentGroup.ItemHiddenInput, {})
3654
- ] }, opt.value))
3655
- ]
3656
- }
3657
- ),
3658
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3659
- ]
3660
- }
3661
- )
3662
- });
3663
- var FieldSelect = createField({
3664
- displayName: "FieldSelect",
3665
- useFieldState: (componentProps, resolved) => {
3666
- const sourceOptions = componentProps.options ?? resolved.options ?? [];
3667
- const normalizedOptions = useMemo(
3668
- () => sourceOptions.map((opt) => ({
3669
- label: opt.label,
3670
- value: String(opt.value),
3671
- disabled: opt.disabled
3672
- })),
3673
- [sourceOptions]
3674
- );
3675
- const collection = useMemo(
3676
- () => createListCollection({
3677
- items: normalizedOptions,
3678
- itemToString: getOptionLabel,
3679
- itemToValue: (item) => item.value
3680
- }),
3681
- [normalizedOptions]
3682
- );
3683
- const resolvedClearable = componentProps.clearable ?? !resolved.required;
3684
- return { collection, normalizedOptions, resolvedClearable };
3685
- },
3686
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3687
- const currentValue = field.state.value;
3688
- const stringValue = currentValue !== null && currentValue !== void 0 ? String(currentValue) : void 0;
3689
- return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, required: resolved.required, disabled: resolved.disabled, children: [
3690
- /* @__PURE__ */ jsxs(
3691
- Select.Root,
3692
- {
3693
- collection: fieldState.collection,
3694
- size: componentProps.size ?? "md",
3695
- variant: componentProps.variant ?? "outline",
3696
- value: stringValue ? [stringValue] : [],
3697
- onValueChange: (details) => {
3698
- const newStringValue = details.value[0];
3699
- if (componentProps.valueType === "number") {
3700
- field.handleChange(newStringValue ? Number(newStringValue) : 0);
3701
- } else {
3702
- field.handleChange(newStringValue ?? "");
3703
- }
3704
- },
3705
- onInteractOutside: () => field.handleBlur(),
3706
- disabled: resolved.disabled,
3707
- "data-field-name": fullPath,
3708
- children: [
3709
- /* @__PURE__ */ jsx(Select.HiddenSelect, {}),
3710
- resolved.label && /* @__PURE__ */ jsx(Select.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3711
- /* @__PURE__ */ jsxs(Select.Control, { children: [
3712
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.ValueText, { placeholder: resolved.placeholder }) }),
3713
- /* @__PURE__ */ jsxs(Select.IndicatorGroup, { children: [
3714
- fieldState.resolvedClearable && /* @__PURE__ */ jsx(Select.ClearTrigger, {}),
3715
- /* @__PURE__ */ jsx(Select.Indicator, {})
3716
- ] })
3717
- ] }),
3718
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { children: /* @__PURE__ */ jsx(Select.Content, { children: fieldState.normalizedOptions.map((opt) => /* @__PURE__ */ jsxs(Select.Item, { item: opt, children: [
3719
- getOptionLabel(opt),
3720
- /* @__PURE__ */ jsx(Select.ItemIndicator, {})
3721
- ] }, opt.value)) }) }) })
3722
- ]
3723
- }
3724
- ),
3725
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3726
- ] });
3727
- }
3728
- });
3729
- var FieldTags = createField({
3730
- displayName: "FieldTags",
3731
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
3732
- const {
3733
- maxTags,
3734
- minTagLength = 1,
3735
- delimiter,
3736
- addOnBlur = false,
3737
- addOnPaste = true,
3738
- editable = false,
3739
- clearable = false,
3740
- size = "md",
3741
- variant = "outline",
3742
- colorPalette
3743
- } = componentProps;
3744
- const value = field.state.value ?? [];
3745
- const handleValueChange = (details) => {
3746
- field.handleChange(details.value);
3747
- };
3748
- const validateTag = (details) => {
3749
- return details.inputValue.length >= minTagLength;
3750
- };
3751
- return /* @__PURE__ */ jsxs(
3752
- Field.Root,
3753
- {
3754
- invalid: hasError,
3755
- required: resolved.required,
3756
- disabled: resolved.disabled,
3757
- readOnly: resolved.readOnly,
3758
- children: [
3759
- /* @__PURE__ */ jsxs(
3760
- TagsInput.Root,
3761
- {
3762
- value,
3763
- onValueChange: handleValueChange,
3764
- max: maxTags,
3765
- validate: validateTag,
3766
- delimiter,
3767
- blurBehavior: addOnBlur ? "add" : void 0,
3768
- addOnPaste,
3769
- editable,
3770
- disabled: resolved.disabled,
3771
- readOnly: resolved.readOnly,
3772
- size,
3773
- variant,
3774
- colorPalette,
3775
- "data-field-name": fullPath,
3776
- children: [
3777
- resolved.label && /* @__PURE__ */ jsx(TagsInput.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
3778
- /* @__PURE__ */ jsxs(TagsInput.Control, { children: [
3779
- /* @__PURE__ */ jsx(TagsInput.Items, {}),
3780
- /* @__PURE__ */ jsx(TagsInput.Input, { placeholder: resolved.placeholder, onBlur: field.handleBlur }),
3781
- clearable && /* @__PURE__ */ jsx(TagsInput.ClearTrigger, {})
3782
- ] }),
3783
- /* @__PURE__ */ jsx(TagsInput.HiddenInput, {})
3784
- ]
3785
- }
3786
- ),
3787
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
3788
- ]
3789
- }
3790
- );
3791
- }
3792
- });
3793
-
3794
- // src/lib/declarative/form-fields/specialized/providers/dadata.ts
3795
- var DADATA_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/address";
3796
- function createDaDataProvider(config) {
3797
- const { token, baseUrl = DADATA_URL } = config;
3798
- return {
3799
- async getSuggestions(query, options) {
3800
- const body = {
3801
- query,
3802
- count: options?.count ?? 10
3803
- };
3804
- if (options?.bounds) {
3805
- if (options.bounds.from) body.from_bound = { value: options.bounds.from };
3806
- if (options.bounds.to) body.to_bound = { value: options.bounds.to };
3807
- }
3808
- if (options?.filters) {
3809
- body.locations = [options.filters];
3810
- }
3811
- const response = await fetch(baseUrl, {
3812
- method: "POST",
3813
- headers: {
3814
- "Content-Type": "application/json",
3815
- Accept: "application/json",
3816
- Authorization: `Token ${token}`
3817
- },
3818
- body: JSON.stringify(body)
3819
- });
3820
- if (!response.ok) {
3821
- return [];
3822
- }
3823
- const data = await response.json();
3824
- const suggestions = data.suggestions ?? [];
3825
- return suggestions.map(
3826
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3827
- (s) => ({
3828
- label: s.value,
3829
- value: s.value,
3830
- data: s.data
3831
- })
3832
- );
3833
- }
3834
- };
3835
- }
3836
- function useAddressProvider(propProvider, token) {
3837
- const formContext2 = useDeclarativeFormOptional();
3838
- if (propProvider) return propProvider;
3839
- if (formContext2?.addressProvider) return formContext2.addressProvider;
3840
- if (token) return createDaDataProvider({ token });
3841
- return null;
3842
- }
3843
- var FieldAddress = createField({
3844
- displayName: "FieldAddress",
3845
- useFieldState: (props) => {
3846
- const { provider: propProvider, token, minChars = 3, debounceMs = 300, locations } = props;
3847
- const provider = useAddressProvider(propProvider, token);
3848
- const [inputValue, setInputValue] = useState("");
3849
- const [suggestions, setSuggestions] = useState([]);
3850
- const [isLoading, setIsLoading] = useState(false);
3851
- const [isOpen, setIsOpen] = useState(false);
3852
- const [highlightedIndex, setHighlightedIndex] = useState(-1);
3853
- const containerRef = useRef(null);
3854
- const initializedRef = useRef(false);
3855
- const debouncedQuery = useDebounce(inputValue, debounceMs);
3856
- const fetchSuggestions = useCallback(
3857
- async (query) => {
3858
- if (query.length < minChars || !provider) {
3859
- setSuggestions([]);
3860
- return;
3861
- }
3862
- setIsLoading(true);
3863
- try {
3864
- const results = await provider.getSuggestions(query, {
3865
- count: 10,
3866
- filters: locations ? Object.assign({}, ...locations) : void 0
3867
- });
3868
- setSuggestions(results);
3869
- setIsOpen(true);
3870
- } catch (error) {
3871
- console.error("Error loading address suggestions:", error);
3872
- setSuggestions([]);
3873
- } finally {
3874
- setIsLoading(false);
3875
- }
3876
- },
3877
- [provider, minChars, locations]
3878
- );
3879
- useEffect(() => {
3880
- if (debouncedQuery) {
3881
- fetchSuggestions(debouncedQuery);
3882
- } else {
3883
- setSuggestions([]);
3884
- setIsOpen(false);
3885
- }
3886
- }, [debouncedQuery, fetchSuggestions]);
3887
- useEffect(() => {
3888
- const handleClickOutside = (event) => {
3889
- if (containerRef.current && !containerRef.current.contains(event.target)) {
3890
- setIsOpen(false);
3891
- }
3892
- };
3893
- document.addEventListener("mousedown", handleClickOutside);
3894
- return () => document.removeEventListener("mousedown", handleClickOutside);
3895
- }, []);
3896
- return {
3897
- inputValue,
3898
- setInputValue,
3899
- suggestions,
3900
- setSuggestions,
3901
- isLoading,
3902
- setIsLoading,
3903
- isOpen,
3904
- setIsOpen,
3905
- highlightedIndex,
3906
- setHighlightedIndex,
3907
- containerRef,
3908
- debouncedQuery,
3909
- fetchSuggestions,
3910
- initializedRef
3911
- };
3912
- },
3913
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
3914
- const { valueOnly = false } = componentProps;
3915
- const {
3916
- inputValue,
3917
- setInputValue,
3918
- suggestions,
3919
- setSuggestions,
3920
- isLoading,
3921
- isOpen,
3922
- setIsOpen,
3923
- highlightedIndex,
3924
- setHighlightedIndex,
3925
- containerRef,
3926
- initializedRef
3927
- } = fieldState;
3928
- const fieldValue = field.state.value;
3929
- if (!initializedRef.current && fieldValue) {
3930
- const displayValue = typeof fieldValue === "string" ? fieldValue : fieldValue.value;
3931
- if (displayValue && displayValue !== inputValue) {
3932
- setInputValue(displayValue);
3933
- }
3934
- initializedRef.current = true;
3935
- }
3936
- const handleSelect = (suggestion) => {
3937
- setInputValue(suggestion.value);
3938
- setIsOpen(false);
3939
- setSuggestions([]);
3940
- if (valueOnly) {
3941
- field.handleChange(suggestion.value);
3942
- } else {
3943
- const addressValue = {
3944
- value: suggestion.value,
3945
- data: suggestion.data
3946
- };
3947
- field.handleChange(addressValue);
3948
- }
3949
- };
3950
- const handleKeyDown = (e) => {
3951
- if (!isOpen || suggestions.length === 0) {
3952
- return;
3953
- }
3954
- switch (e.key) {
3955
- case "ArrowDown":
3956
- e.preventDefault();
3957
- setHighlightedIndex(highlightedIndex < suggestions.length - 1 ? highlightedIndex + 1 : 0);
3958
- break;
3959
- case "ArrowUp":
3960
- e.preventDefault();
3961
- setHighlightedIndex(highlightedIndex > 0 ? highlightedIndex - 1 : suggestions.length - 1);
3962
- break;
3963
- case "Enter":
3964
- e.preventDefault();
3965
- if (highlightedIndex >= 0) {
3966
- handleSelect(suggestions[highlightedIndex]);
3967
- }
3968
- break;
3969
- case "Escape":
3970
- setIsOpen(false);
3971
- break;
3972
- }
3973
- };
3974
- return /* @__PURE__ */ jsxs(
3975
- Field.Root,
3976
- {
3977
- invalid: hasError,
3978
- required: resolved.required,
3979
- disabled: resolved.disabled,
3980
- readOnly: resolved.readOnly,
3981
- children: [
3982
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
3983
- /* @__PURE__ */ jsxs(Box, { ref: containerRef, position: "relative", width: "100%", children: [
3984
- /* @__PURE__ */ jsx(
3985
- Input,
3986
- {
3987
- value: inputValue,
3988
- onChange: (e) => {
3989
- setInputValue(e.target.value);
3990
- setHighlightedIndex(-1);
3991
- },
3992
- onFocus: () => {
3993
- if (suggestions.length > 0) {
3994
- setIsOpen(true);
3995
- }
3996
- },
3997
- onBlur: field.handleBlur,
3998
- onKeyDown: handleKeyDown,
3999
- placeholder: resolved.placeholder ?? "Start typing address...",
4000
- "data-field-name": fullPath
4001
- }
4002
- ),
4003
- isLoading && /* @__PURE__ */ jsx(Box, { position: "absolute", right: 3, top: "50%", transform: "translateY(-50%)", children: /* @__PURE__ */ jsx(Spinner, { size: "sm" }) }),
4004
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
4005
- List.Root,
4006
- {
4007
- position: "absolute",
4008
- zIndex: 10,
4009
- width: "100%",
4010
- bg: "bg.panel",
4011
- borderWidth: "1px",
4012
- borderRadius: "md",
4013
- shadow: "md",
4014
- maxH: "200px",
4015
- overflowY: "auto",
4016
- mt: 1,
4017
- children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
4018
- List.Item,
4019
- {
4020
- px: 3,
4021
- py: 2,
4022
- cursor: "pointer",
4023
- bg: highlightedIndex === index ? "bg.muted" : void 0,
4024
- _hover: { bg: "bg.muted" },
4025
- onClick: () => handleSelect(suggestion),
4026
- onMouseEnter: () => setHighlightedIndex(index),
4027
- children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: suggestion.label })
4028
- },
4029
- suggestion.value + index
4030
- ))
4031
- }
4032
- )
4033
- ] }),
4034
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
4035
- ]
4036
- }
4037
- );
4038
- }
4039
- });
4040
- var defaultSwatches = [
4041
- "#000000",
4042
- "#4A5568",
4043
- "#F56565",
4044
- "#ED64A6",
4045
- "#9F7AEA",
4046
- "#6B46C1",
4047
- "#4299E1",
4048
- "#0BC5EA",
4049
- "#38B2AC",
4050
- "#48BB78",
4051
- "#ECC94B",
4052
- "#DD6B20"
4053
- ];
4054
- var FieldColorPicker = createField({
4055
- displayName: "FieldColorPicker",
4056
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
4057
- const {
4058
- swatches = defaultSwatches,
4059
- size = "md",
4060
- showArea = true,
4061
- showEyeDropper = true,
4062
- showSliders = true,
4063
- showInput = true
4064
- } = componentProps;
4065
- const currentValue = field.state.value || "#000000";
4066
- let parsedColor;
4067
- try {
4068
- parsedColor = parseColor(currentValue);
4069
- } catch {
4070
- parsedColor = parseColor("#000000");
4071
- }
4072
- return /* @__PURE__ */ jsxs(
4073
- Field.Root,
4074
- {
4075
- invalid: hasError,
4076
- required: resolved.required,
4077
- disabled: resolved.disabled,
4078
- readOnly: resolved.readOnly,
4079
- children: [
4080
- /* @__PURE__ */ jsxs(
4081
- ColorPicker.Root,
4082
- {
4083
- value: parsedColor,
4084
- onValueChange: (details) => {
4085
- field.handleChange(details.valueAsString);
4086
- },
4087
- disabled: resolved.disabled,
4088
- readOnly: resolved.readOnly,
4089
- size,
4090
- children: [
4091
- /* @__PURE__ */ jsx(ColorPicker.HiddenInput, { name: fullPath }),
4092
- resolved.label && /* @__PURE__ */ jsxs(ColorPicker.Label, { children: [
4093
- resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
4094
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
4095
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
4096
- ] }) : resolved.label,
4097
- resolved.required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
4098
- ] }),
4099
- /* @__PURE__ */ jsxs(ColorPicker.Control, { children: [
4100
- showInput && /* @__PURE__ */ jsx(ColorPicker.ChannelInput, { channel: "hex" }),
4101
- /* @__PURE__ */ jsx(ColorPicker.Trigger, {})
4102
- ] }),
4103
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(ColorPicker.Positioner, { children: /* @__PURE__ */ jsxs(ColorPicker.Content, { children: [
4104
- showArea && /* @__PURE__ */ jsx(ColorPicker.Area, {}),
4105
- (showEyeDropper || showSliders) && /* @__PURE__ */ jsxs(HStack, { children: [
4106
- showEyeDropper && /* @__PURE__ */ jsx(ColorPicker.EyeDropper, { size: "xs", variant: "outline" }),
4107
- showSliders && /* @__PURE__ */ jsx(ColorPicker.Sliders, {})
4108
- ] }),
4109
- swatches.length > 0 && /* @__PURE__ */ jsx(ColorPicker.SwatchGroup, { children: swatches.map((swatch) => /* @__PURE__ */ jsx(ColorPicker.SwatchTrigger, { value: swatch, children: /* @__PURE__ */ jsx(ColorPicker.Swatch, { value: swatch, boxSize: "4.5" }) }, swatch)) })
4110
- ] }) }) })
4111
- ]
4112
- }
4113
- ),
4114
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
4115
- ]
4116
- }
4117
- );
4118
- }
4119
- });
4120
- function FileImageList({ clearable }) {
4121
- const fileUpload = useFileUploadContext();
4122
- if (fileUpload.acceptedFiles.length === 0) {
4123
- return null;
4124
- }
4125
- return /* @__PURE__ */ jsx(HStack, { wrap: "wrap", gap: "3", mt: "2", children: fileUpload.acceptedFiles.map((file) => /* @__PURE__ */ jsxs(FileUpload.Item, { file, p: "2", width: "auto", pos: "relative", children: [
4126
- clearable && /* @__PURE__ */ jsx(Float, { placement: "top-end", children: /* @__PURE__ */ jsx(FileUpload.ItemDeleteTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "2xs", variant: "solid", colorPalette: "red", rounded: "full", children: /* @__PURE__ */ jsx(LuX, {}) }) }) }),
4127
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { type: "image/*", asChild: true, children: /* @__PURE__ */ jsx(FileUpload.ItemPreviewImage, { boxSize: "16", rounded: "md", objectFit: "cover" }) }),
4128
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { type: ".*", asChild: true, children: /* @__PURE__ */ jsx(Icon, { fontSize: "4xl", color: "fg.muted", children: /* @__PURE__ */ jsx(LuFile, {}) }) })
4129
- ] }, file.name)) });
4130
- }
4131
- function FileList({ showSize, clearable }) {
4132
- const fileUpload = useFileUploadContext();
4133
- if (fileUpload.acceptedFiles.length === 0) {
4134
- return null;
4135
- }
4136
- return /* @__PURE__ */ jsx(FileUpload.ItemGroup, { mt: "2", children: fileUpload.acceptedFiles.map((file) => /* @__PURE__ */ jsxs(FileUpload.Item, { file, children: [
4137
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { asChild: true, children: /* @__PURE__ */ jsx(Icon, { fontSize: "lg", color: "fg.muted", children: /* @__PURE__ */ jsx(LuFile, {}) }) }),
4138
- showSize ? /* @__PURE__ */ jsxs(FileUpload.ItemContent, { children: [
4139
- /* @__PURE__ */ jsx(FileUpload.ItemName, {}),
4140
- /* @__PURE__ */ jsx(FileUpload.ItemSizeText, {})
4141
- ] }) : /* @__PURE__ */ jsx(FileUpload.ItemName, { flex: "1" }),
4142
- clearable && /* @__PURE__ */ jsx(FileUpload.ItemDeleteTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "ghost", color: "fg.muted", size: "xs", children: /* @__PURE__ */ jsx(LuX, {}) }) })
4143
- ] }, file.name)) });
4144
- }
4145
- var FieldFileUpload = createField({
4146
- displayName: "FieldFileUpload",
4147
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
4148
- const {
4149
- accept,
4150
- maxFileSize,
4151
- maxFiles = 1,
4152
- variant = "button",
4153
- showSize = false,
4154
- clearable = true,
4155
- dropzoneLabel = "Drag and drop files here",
4156
- dropzoneDescription,
4157
- buttonText = "Upload file"
4158
- } = componentProps;
4159
- const placeholder = resolved.placeholder ?? "Select file(s)";
4160
- const normalizedAccept = accept ? typeof accept === "string" ? accept.split(",").map((s) => s.trim()) : accept : void 0;
4161
- const isImageUpload = normalizedAccept?.some((type) => type.startsWith("image/") || type === "image/*");
4162
- return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, required: resolved.required, disabled: resolved.disabled, children: [
4163
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
4164
- /* @__PURE__ */ jsxs(
4165
- FileUpload.Root,
4166
- {
4167
- maxFiles,
4168
- maxFileSize,
4169
- accept: normalizedAccept,
4170
- disabled: resolved.disabled,
4171
- onFileChange: (details) => {
4172
- field.handleChange(details.acceptedFiles);
4173
- },
4174
- "data-field-name": fullPath,
4175
- children: [
4176
- /* @__PURE__ */ jsx(FileUpload.HiddenInput, { onBlur: field.handleBlur }),
4177
- variant === "button" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
4178
- /* @__PURE__ */ jsx(FileUpload.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
4179
- /* @__PURE__ */ jsx(LuUpload, {}),
4180
- buttonText
4181
- ] }) }),
4182
- isImageUpload ? /* @__PURE__ */ jsx(FileImageList, { clearable }) : /* @__PURE__ */ jsx(FileList, { showSize, clearable })
4183
- ] }),
4184
- variant === "dropzone" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
4185
- /* @__PURE__ */ jsxs(FileUpload.Dropzone, { children: [
4186
- /* @__PURE__ */ jsx(Icon, { size: "md", color: "fg.muted", children: /* @__PURE__ */ jsx(LuUpload, {}) }),
4187
- /* @__PURE__ */ jsxs(FileUpload.DropzoneContent, { children: [
4188
- /* @__PURE__ */ jsx(Box, { children: dropzoneLabel }),
4189
- dropzoneDescription && /* @__PURE__ */ jsx(Text, { color: "fg.muted", children: dropzoneDescription })
4190
- ] })
4191
- ] }),
4192
- isImageUpload ? /* @__PURE__ */ jsx(FileImageList, { clearable }) : /* @__PURE__ */ jsx(FileList, { showSize, clearable })
4193
- ] }),
4194
- variant === "input" && /* @__PURE__ */ jsx(Input, { asChild: true, children: /* @__PURE__ */ jsx(FileUpload.Trigger, { children: /* @__PURE__ */ jsx(FileUpload.Context, { children: ({ acceptedFiles }) => {
4195
- if (acceptedFiles.length === 1) {
4196
- return /* @__PURE__ */ jsx("span", { children: acceptedFiles[0].name });
4197
- }
4198
- if (acceptedFiles.length > 1) {
4199
- return /* @__PURE__ */ jsxs("span", { children: [
4200
- acceptedFiles.length,
4201
- " files"
4202
- ] });
4203
- }
4204
- return /* @__PURE__ */ jsx(Text, { color: "fg.subtle", children: placeholder });
4205
- } }) }) })
4206
- ]
4207
- }
4208
- ),
4209
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
4210
- ] });
4211
- }
4212
- });
4213
- var FieldOTPInput = createField({
4214
- displayName: "FieldOTPInput",
4215
- useFieldState: (props) => {
4216
- const [countdown, setCountdown] = useState(0);
4217
- const [isResending, setIsResending] = useState(false);
4218
- useEffect(() => {
4219
- if (countdown <= 0) {
4220
- return;
4221
- }
4222
- const timer = setInterval(() => {
4223
- setCountdown((prev) => prev - 1);
4224
- }, 1e3);
4225
- return () => clearInterval(timer);
4226
- }, [countdown]);
4227
- const handleResend = useCallback(async () => {
4228
- if (!props.onResend || countdown > 0) {
245
+ const shouldBlock = onBlock?.();
246
+ if (shouldBlock === false) {
4229
247
  return;
4230
248
  }
4231
- setIsResending(true);
4232
- try {
4233
- await props.onResend();
4234
- setCountdown(props.resendTimeout ?? 60);
4235
- } finally {
4236
- setIsResending(false);
4237
- }
4238
- }, [props.onResend, countdown, props.resendTimeout]);
4239
- const formatCountdown = (seconds) => {
4240
- const mins = Math.floor(seconds / 60);
4241
- const secs = seconds % 60;
4242
- return `${mins}:${secs.toString().padStart(2, "0")}`;
4243
- };
4244
- const formContext2 = useDeclarativeForm();
4245
- return { countdown, isResending, handleResend, formatCountdown, formContext: formContext2 };
4246
- },
4247
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
4248
- const { length = 6, autoSubmit = false, type = "numeric", mask = false, onResend } = componentProps;
4249
- const { countdown, isResending, handleResend, formatCountdown, formContext: formContext2 } = fieldState;
4250
- const value = field.state.value ?? "";
4251
- const handleValueComplete = (details) => {
4252
- field.handleChange(details.valueAsString);
4253
- if (autoSubmit && details.valueAsString.length === length) {
4254
- formContext2.form.handleSubmit();
4255
- }
249
+ event.preventDefault();
250
+ event.stopPropagation();
251
+ pendingHref.current = href;
252
+ setShowDialog(true);
4256
253
  };
4257
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(Box, { children: [
4258
- /* @__PURE__ */ jsxs(
4259
- PinInput.Root,
4260
- {
4261
- value: value.split(""),
4262
- onValueComplete: handleValueComplete,
4263
- onValueChange: (details) => field.handleChange(details.valueAsString),
4264
- count: length,
4265
- type,
4266
- mask,
4267
- otp: true,
4268
- children: [
4269
- /* @__PURE__ */ jsx(PinInput.Control, { children: /* @__PURE__ */ jsx(HStack, { gap: 2, children: Array.from({ length }).map((_, index) => /* @__PURE__ */ jsx(PinInput.Input, { index, "data-field-name": index === 0 ? fullPath : void 0 }, index)) }) }),
4270
- /* @__PURE__ */ jsx(PinInput.HiddenInput, {})
4271
- ]
4272
- }
4273
- ),
4274
- onResend && /* @__PURE__ */ jsx(HStack, { mt: 3, justify: "center", children: countdown > 0 ? /* @__PURE__ */ jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [
4275
- "Redo in ",
4276
- formatCountdown(countdown)
4277
- ] }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: handleResend, disabled: isResending, loading: isResending, children: "Submit again" }) })
4278
- ] }) });
254
+ document.addEventListener("click", handleClick, { capture: true });
255
+ return () => document.removeEventListener("click", handleClick, { capture: true });
256
+ }, [enabled, onBlock, checkIsDirty]);
257
+ const handleConfirm = useCallback(() => {
258
+ setShowDialog(false);
259
+ if (pendingHref.current) {
260
+ form.reset();
261
+ router.push(pendingHref.current);
262
+ pendingHref.current = null;
263
+ }
264
+ }, [form, router]);
265
+ const handleCancel = useCallback(() => {
266
+ setShowDialog(false);
267
+ pendingHref.current = null;
268
+ }, []);
269
+ if (!showDialog) {
270
+ return null;
4279
271
  }
4280
- });
4281
- var PHONE_MASKS = {
4282
- RU: "+7 (999) 999-99-99",
4283
- US: "+1 (999) 999-9999",
4284
- UK: "+44 9999 999999",
4285
- DE: "+49 999 99999999",
4286
- FR: "+33 9 99 99 99 99",
4287
- IT: "+39 999 999 9999",
4288
- ES: "+34 999 99 99 99",
4289
- CN: "+86 999 9999 9999",
4290
- JP: "+81 99 9999 9999",
4291
- KR: "+82 99 9999 9999",
4292
- BY: "+375 (99) 999-99-99",
4293
- KZ: "+7 (999) 999-99-99",
4294
- UA: "+380 (99) 999-99-99"
4295
- };
4296
- var COUNTRY_FLAGS = {
4297
- RU: "\u{1F1F7}\u{1F1FA}",
4298
- US: "\u{1F1FA}\u{1F1F8}",
4299
- UK: "\u{1F1EC}\u{1F1E7}",
4300
- DE: "\u{1F1E9}\u{1F1EA}",
4301
- FR: "\u{1F1EB}\u{1F1F7}",
4302
- IT: "\u{1F1EE}\u{1F1F9}",
4303
- ES: "\u{1F1EA}\u{1F1F8}",
4304
- CN: "\u{1F1E8}\u{1F1F3}",
4305
- JP: "\u{1F1EF}\u{1F1F5}",
4306
- KR: "\u{1F1F0}\u{1F1F7}",
4307
- BY: "\u{1F1E7}\u{1F1FE}",
4308
- KZ: "\u{1F1F0}\u{1F1FF}",
4309
- UA: "\u{1F1FA}\u{1F1E6}"
4310
- };
4311
- var FieldPhone = createField({
4312
- displayName: "FieldPhone",
4313
- useFieldState: (props) => {
4314
- const { country = "RU", autoUnmask = false } = props;
4315
- const mask = PHONE_MASKS[country];
4316
- const maskRef = useCallback(
4317
- (element) => {
4318
- if (element && mask) {
4319
- const maskCallback = withMask(mask, {
4320
- showMaskOnFocus: true,
4321
- clearIncomplete: true,
4322
- autoUnmask
4323
- });
4324
- maskCallback(element);
4325
- }
272
+ return /* @__PURE__ */ jsx(
273
+ "div",
274
+ {
275
+ style: {
276
+ position: "fixed",
277
+ inset: 0,
278
+ zIndex: 9999,
279
+ display: "flex",
280
+ alignItems: "center",
281
+ justifyContent: "center",
282
+ backgroundColor: "rgba(0, 0, 0, 0.5)"
4326
283
  },
4327
- [mask, autoUnmask]
4328
- );
4329
- return { maskRef };
4330
- },
4331
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
4332
- const { country = "RU", showFlag = false } = componentProps;
4333
- const flag = COUNTRY_FLAGS[country];
4334
- const mask = PHONE_MASKS[country];
4335
- const value = field.state.value ?? "";
4336
- const resolvedPlaceholder = resolved.placeholder ?? mask?.toString().replace(/9/g, "_");
4337
- return /* @__PURE__ */ jsxs(
4338
- Field.Root,
4339
- {
4340
- invalid: hasError,
4341
- required: resolved.required,
4342
- disabled: resolved.disabled,
4343
- readOnly: resolved.readOnly,
4344
- children: [
4345
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
4346
- /* @__PURE__ */ jsxs(Group, { attached: true, children: [
4347
- showFlag && /* @__PURE__ */ jsx(Text, { px: 3, display: "flex", alignItems: "center", bg: "bg.muted", borderWidth: "1px", borderRightWidth: "0", children: flag }),
284
+ onClick: handleCancel,
285
+ children: /* @__PURE__ */ jsxs(
286
+ "div",
287
+ {
288
+ style: {
289
+ backgroundColor: "var(--chakra-colors-bg-panel, white)",
290
+ borderRadius: "12px",
291
+ padding: "24px",
292
+ maxWidth: "400px",
293
+ width: "90%",
294
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)"
295
+ },
296
+ onClick: (e) => e.stopPropagation(),
297
+ children: [
298
+ /* @__PURE__ */ jsx(
299
+ "h2",
300
+ {
301
+ style: {
302
+ margin: "0 0 8px 0",
303
+ fontSize: "1.125rem",
304
+ fontWeight: 600,
305
+ color: "var(--chakra-colors-fg, inherit)"
306
+ },
307
+ children: dialogTitle
308
+ }
309
+ ),
4348
310
  /* @__PURE__ */ jsx(
4349
- Input,
311
+ "p",
312
+ {
313
+ style: {
314
+ margin: "0 0 24px 0",
315
+ fontSize: "0.875rem",
316
+ color: "var(--chakra-colors-fg-muted, #666)"
317
+ },
318
+ children: dialogDescription
319
+ }
320
+ ),
321
+ /* @__PURE__ */ jsxs(
322
+ "div",
4350
323
  {
4351
- ref: fieldState.maskRef,
4352
- value,
4353
- onChange: (e) => field.handleChange(e.target.value),
4354
- onBlur: field.handleBlur,
4355
- placeholder: resolvedPlaceholder,
4356
- "data-field-name": fullPath,
4357
- type: "tel",
4358
- inputMode: "tel",
4359
- autoComplete: "tel"
324
+ style: {
325
+ display: "flex",
326
+ gap: "12px",
327
+ justifyContent: "flex-end"
328
+ },
329
+ children: [
330
+ /* @__PURE__ */ jsx(
331
+ "button",
332
+ {
333
+ onClick: handleCancel,
334
+ style: {
335
+ padding: "8px 16px",
336
+ borderRadius: "6px",
337
+ border: "1px solid var(--chakra-colors-border, #e2e8f0)",
338
+ backgroundColor: "transparent",
339
+ cursor: "pointer",
340
+ fontSize: "0.875rem",
341
+ fontWeight: 500
342
+ },
343
+ children: cancelText
344
+ }
345
+ ),
346
+ /* @__PURE__ */ jsx(
347
+ "button",
348
+ {
349
+ onClick: handleConfirm,
350
+ style: {
351
+ padding: "8px 16px",
352
+ borderRadius: "6px",
353
+ border: "none",
354
+ backgroundColor: "var(--chakra-colors-red-500, #e53e3e)",
355
+ color: "white",
356
+ cursor: "pointer",
357
+ fontSize: "0.875rem",
358
+ fontWeight: 500
359
+ },
360
+ children: confirmText
361
+ }
362
+ )
363
+ ]
4360
364
  }
4361
365
  )
4362
- ] }),
4363
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
4364
- ]
4365
- }
4366
- );
4367
- }
4368
- });
4369
- var FieldPinInput = createField({
4370
- displayName: "FieldPinInput",
4371
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
4372
- const {
4373
- count = 4,
4374
- mask,
4375
- otp,
4376
- type = "numeric",
4377
- size = "md",
4378
- variant = "outline",
4379
- attached,
4380
- onComplete
4381
- } = componentProps;
4382
- const stringValue = field.state.value ?? "";
4383
- const arrayValue = stringValue.split("").slice(0, count);
4384
- while (arrayValue.length < count) {
4385
- arrayValue.push("");
4386
- }
4387
- const handleValueChange = (details) => {
4388
- const newValue = details.value.join("");
4389
- field.handleChange(newValue);
4390
- };
4391
- const handleValueComplete = (details) => {
4392
- const completeValue = details.value.join("");
4393
- onComplete?.(completeValue);
4394
- };
4395
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
4396
- PinInput.Root,
4397
- {
4398
- value: arrayValue,
4399
- onValueChange: handleValueChange,
4400
- onValueComplete: handleValueComplete,
4401
- placeholder: resolved.placeholder,
4402
- mask,
4403
- otp,
4404
- type,
4405
- size,
4406
- variant,
4407
- attached,
4408
- disabled: resolved.disabled,
4409
- readOnly: resolved.readOnly,
4410
- invalid: hasError,
4411
- count,
4412
- onBlur: field.handleBlur,
4413
- "data-field-name": fullPath,
4414
- children: [
4415
- /* @__PURE__ */ jsx(PinInput.HiddenInput, {}),
4416
- /* @__PURE__ */ jsx(PinInput.Control, { children: Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx(PinInput.Input, { index }, index)) })
4417
- ]
4418
- }
4419
- ) });
4420
- }
4421
- });
4422
- var FieldMaskedInput = createField({
4423
- displayName: "FieldMaskedInput",
4424
- useFieldState: (props) => {
4425
- const {
4426
- mask,
4427
- placeholderChar = "_",
4428
- showMaskOnFocus = true,
4429
- showMaskOnHover = false,
4430
- clearIncomplete = false,
4431
- autoUnmask = false
4432
- } = props;
4433
- const maskRef = useCallback(
4434
- (element) => {
4435
- if (element && mask) {
4436
- const maskCallback = withMask(mask, {
4437
- placeholder: placeholderChar,
4438
- showMaskOnFocus,
4439
- showMaskOnHover,
4440
- clearIncomplete,
4441
- autoUnmask
4442
- });
4443
- maskCallback(element);
366
+ ]
4444
367
  }
4445
- },
4446
- [mask, placeholderChar, showMaskOnFocus, showMaskOnHover, clearIncomplete, autoUnmask]
4447
- );
4448
- return { maskRef };
4449
- },
4450
- render: ({ field, fullPath, resolved, hasError, errorMessage, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
4451
- Input,
4452
- {
4453
- ref: fieldState.maskRef,
4454
- value: field.state.value ?? "",
4455
- onChange: (e) => field.handleChange(e.target.value),
4456
- onBlur: field.handleBlur,
4457
- placeholder: resolved.placeholder,
4458
- "data-field-name": fullPath
368
+ )
4459
369
  }
4460
- ) })
4461
- });
370
+ );
371
+ }
4462
372
  function getZodType(schema) {
4463
373
  if (!schema?._zod?.def) {
4464
374
  return void 0;
@@ -4504,7 +414,7 @@ function getUIMeta(schema) {
4504
414
  return void 0;
4505
415
  }
4506
416
  }
4507
- function getSchemaAtPath3(schema, path) {
417
+ function getSchemaAtPath(schema, path) {
4508
418
  if (!schema || !path) {
4509
419
  return schema;
4510
420
  }
@@ -4546,7 +456,7 @@ function FieldAuto({ name, config, ...baseProps }) {
4546
456
  throw new Error("Form.Field.Auto requires a name prop");
4547
457
  }
4548
458
  const fullPath = parentGroup ? `${parentGroup.name}.${name}` : name;
4549
- const fieldSchema = getSchemaAtPath3(schema, fullPath);
459
+ const fieldSchema = getSchemaAtPath(schema, fullPath);
4550
460
  const zodType = getZodType(fieldSchema);
4551
461
  const enumValues = getEnumValues(fieldSchema);
4552
462
  const maxLength = getMaxLength(fieldSchema);
@@ -5431,6 +1341,7 @@ function ButtonReset({
5431
1341
  }
5432
1342
  function ButtonSubmit({
5433
1343
  children = "Submit",
1344
+ loadingText,
5434
1345
  disabled,
5435
1346
  colorPalette,
5436
1347
  size,
@@ -5443,6 +1354,7 @@ function ButtonSubmit({
5443
1354
  {
5444
1355
  type: "submit",
5445
1356
  loading: isSubmitting,
1357
+ loadingText,
5446
1358
  disabled: disabled || isSubmitting,
5447
1359
  colorPalette,
5448
1360
  size,
@@ -5452,376 +1364,6 @@ function ButtonSubmit({
5452
1364
  }
5453
1365
  ) });
5454
1366
  }
5455
- function CascadingSelectContent({
5456
- parentValue,
5457
- form,
5458
- fullPath,
5459
- resolved,
5460
- loadOptions,
5461
- initialOptions,
5462
- clearOnParentChange,
5463
- disableWhenParentEmpty,
5464
- clearable,
5465
- size,
5466
- variant,
5467
- placeholderWhenDisabled
5468
- }) {
5469
- const [options, setOptions] = useState(initialOptions);
5470
- const [isLoading, setIsLoading] = useState(false);
5471
- const prevParentValueRef = useRef(parentValue);
5472
- const loadOptionsRef = useRef(loadOptions);
5473
- loadOptionsRef.current = loadOptions;
5474
- useEffect(() => {
5475
- const doLoad = async () => {
5476
- if (parentValue === void 0 || parentValue === null || parentValue === "") {
5477
- setOptions(initialOptions);
5478
- return;
5479
- }
5480
- setIsLoading(true);
5481
- try {
5482
- const result = await loadOptionsRef.current(parentValue);
5483
- const newOptions = Array.isArray(result) ? result : result.options;
5484
- setOptions(newOptions);
5485
- } catch (error) {
5486
- console.error("Error loading cascading select options:", error);
5487
- setOptions([]);
5488
- } finally {
5489
- setIsLoading(false);
5490
- }
5491
- };
5492
- void doLoad();
5493
- }, [parentValue, initialOptions]);
5494
- useEffect(() => {
5495
- if (clearOnParentChange && prevParentValueRef.current !== parentValue) {
5496
- if (prevParentValueRef.current !== void 0) {
5497
- form.setFieldValue(fullPath, "");
5498
- }
5499
- prevParentValueRef.current = parentValue;
5500
- }
5501
- }, [parentValue, clearOnParentChange, form, fullPath]);
5502
- const isParentEmpty = parentValue === void 0 || parentValue === null || parentValue === "";
5503
- const isDisabled = resolved.disabled || disableWhenParentEmpty && isParentEmpty;
5504
- const effectivePlaceholder = isParentEmpty && placeholderWhenDisabled ? placeholderWhenDisabled : resolved.placeholder;
5505
- const resolvedClearable = clearable ?? !resolved.required;
5506
- const collection = useMemo(
5507
- () => createListCollection({
5508
- items: options,
5509
- itemToString: getOptionLabel,
5510
- itemToValue: (item) => item.value
5511
- }),
5512
- [options]
5513
- );
5514
- return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
5515
- const { hasError, errorMessage } = getFieldErrors(field);
5516
- const currentValue = field.state.value;
5517
- return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, required: resolved.required, disabled: isDisabled, children: [
5518
- /* @__PURE__ */ jsxs(
5519
- Select.Root,
5520
- {
5521
- collection,
5522
- size,
5523
- variant,
5524
- value: currentValue ? [currentValue] : [],
5525
- onValueChange: (details) => {
5526
- const newValue = details.value[0];
5527
- field.handleChange(newValue ?? "");
5528
- },
5529
- onInteractOutside: () => field.handleBlur(),
5530
- disabled: isDisabled,
5531
- "data-field-name": fullPath,
5532
- children: [
5533
- /* @__PURE__ */ jsx(Select.HiddenSelect, {}),
5534
- resolved.label && /* @__PURE__ */ jsx(Select.Label, { children: /* @__PURE__ */ jsx(SelectionFieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }) }),
5535
- /* @__PURE__ */ jsxs(Select.Control, { children: [
5536
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.ValueText, { placeholder: effectivePlaceholder }) }),
5537
- /* @__PURE__ */ jsxs(Select.IndicatorGroup, { children: [
5538
- isLoading && /* @__PURE__ */ jsx(Spinner, { size: "xs" }),
5539
- resolvedClearable && !isLoading && /* @__PURE__ */ jsx(Select.ClearTrigger, {}),
5540
- /* @__PURE__ */ jsx(Select.Indicator, {})
5541
- ] })
5542
- ] }),
5543
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { children: /* @__PURE__ */ jsx(Select.Content, { children: options.map((opt) => /* @__PURE__ */ jsxs(Select.Item, { item: opt, children: [
5544
- getOptionLabel(opt),
5545
- /* @__PURE__ */ jsx(Select.ItemIndicator, {})
5546
- ] }, opt.value)) }) }) })
5547
- ]
5548
- }
5549
- ),
5550
- /* @__PURE__ */ jsx(FieldError, { hasError: !!hasError, errorMessage, helperText: resolved.helperText })
5551
- ] });
5552
- } });
5553
- }
5554
- function FieldCascadingSelect(props) {
5555
- const {
5556
- name,
5557
- dependsOn,
5558
- loadOptions,
5559
- initialOptions = [],
5560
- clearOnParentChange = true,
5561
- disableWhenParentEmpty = true,
5562
- clearable,
5563
- size = "md",
5564
- variant = "outline",
5565
- placeholderWhenDisabled,
5566
- ...baseProps
5567
- } = props;
5568
- const { form } = useDeclarativeForm();
5569
- const parentGroup = useFormGroup();
5570
- const { form: _formFromProps, fullPath, ...resolvedRest } = useResolvedFieldProps(name, baseProps);
5571
- const resolved = {
5572
- label: resolvedRest.label,
5573
- placeholder: resolvedRest.placeholder,
5574
- helperText: resolvedRest.helperText,
5575
- tooltip: resolvedRest.tooltip,
5576
- required: resolvedRest.required,
5577
- disabled: resolvedRest.disabled,
5578
- readOnly: resolvedRest.readOnly,
5579
- constraints: resolvedRest.constraints,
5580
- options: resolvedRest.options
5581
- };
5582
- const fullDependsOnPath = parentGroup ? `${parentGroup.name}.${dependsOn}` : dependsOn;
5583
- const parentSelector = (state) => {
5584
- const parts = fullDependsOnPath.split(".");
5585
- let value = state.values;
5586
- for (const part of parts) {
5587
- if (value && typeof value === "object") {
5588
- value = value[part];
5589
- } else {
5590
- value = void 0;
5591
- break;
5592
- }
5593
- }
5594
- return value;
5595
- };
5596
- return /* @__PURE__ */ jsx(form.Subscribe, { selector: parentSelector, children: (parentValue) => /* @__PURE__ */ jsx(
5597
- CascadingSelectContent,
5598
- {
5599
- parentValue,
5600
- form,
5601
- fullPath,
5602
- resolved,
5603
- loadOptions,
5604
- initialOptions,
5605
- clearOnParentChange,
5606
- disableWhenParentEmpty,
5607
- clearable,
5608
- size,
5609
- variant,
5610
- placeholderWhenDisabled
5611
- }
5612
- ) });
5613
- }
5614
- FieldCascadingSelect.displayName = "FieldCascadingSelect";
5615
- function useCityProvider(propProvider, token) {
5616
- const formContext2 = useDeclarativeFormOptional();
5617
- if (propProvider) return propProvider;
5618
- if (formContext2?.addressProvider) return formContext2.addressProvider;
5619
- if (token) return createDaDataProvider({ token });
5620
- const envKey = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_DADATA_API_KEY : "";
5621
- if (envKey) return createDaDataProvider({ token: envKey });
5622
- return null;
5623
- }
5624
- var FieldCity = createField({
5625
- displayName: "FieldCity",
5626
- useFieldState: (props) => {
5627
- const { provider: propProvider, token, minChars = 2, debounceMs = 300 } = props;
5628
- const provider = useCityProvider(propProvider, token);
5629
- const [inputValue, setInputValue] = useState("");
5630
- const [suggestions, setSuggestions] = useState([]);
5631
- const [isLoading, setIsLoading] = useState(false);
5632
- const [isOpen, setIsOpen] = useState(false);
5633
- const [highlightedIndex, setHighlightedIndex] = useState(-1);
5634
- const containerRef = useRef(null);
5635
- const debouncedQuery = useDebounce(inputValue, debounceMs);
5636
- const justSelectedRef = useRef(false);
5637
- const initializedRef = useRef(false);
5638
- const fetchSuggestions = useCallback(
5639
- async (query) => {
5640
- if (query.length < minChars || !provider) {
5641
- setSuggestions([]);
5642
- return;
5643
- }
5644
- setIsLoading(true);
5645
- try {
5646
- const results = await provider.getSuggestions(query, {
5647
- count: 7,
5648
- bounds: { from: "city", to: "settlement" }
5649
- });
5650
- setSuggestions(results);
5651
- setIsOpen(results.length > 0);
5652
- } catch (error) {
5653
- console.error("Error loading city suggestions:", error);
5654
- setSuggestions([]);
5655
- } finally {
5656
- setIsLoading(false);
5657
- }
5658
- },
5659
- [provider, minChars]
5660
- );
5661
- useEffect(() => {
5662
- if (justSelectedRef.current) {
5663
- justSelectedRef.current = false;
5664
- return;
5665
- }
5666
- if (debouncedQuery) {
5667
- fetchSuggestions(debouncedQuery);
5668
- } else {
5669
- setSuggestions([]);
5670
- setIsOpen(false);
5671
- }
5672
- }, [debouncedQuery, fetchSuggestions]);
5673
- useEffect(() => {
5674
- const handleClickOutside = (event) => {
5675
- if (containerRef.current && !containerRef.current.contains(event.target)) {
5676
- setIsOpen(false);
5677
- }
5678
- };
5679
- document.addEventListener("mousedown", handleClickOutside);
5680
- return () => document.removeEventListener("mousedown", handleClickOutside);
5681
- }, []);
5682
- return {
5683
- inputValue,
5684
- setInputValue,
5685
- suggestions,
5686
- setSuggestions,
5687
- isLoading,
5688
- setIsLoading,
5689
- isOpen,
5690
- setIsOpen,
5691
- highlightedIndex,
5692
- setHighlightedIndex,
5693
- containerRef,
5694
- debouncedQuery,
5695
- justSelectedRef,
5696
- initializedRef
5697
- };
5698
- },
5699
- render: ({ field, fullPath, resolved, hasError, errorMessage, fieldState }) => {
5700
- const {
5701
- inputValue,
5702
- setInputValue,
5703
- suggestions,
5704
- setSuggestions,
5705
- isLoading,
5706
- isOpen,
5707
- setIsOpen,
5708
- highlightedIndex,
5709
- setHighlightedIndex,
5710
- containerRef
5711
- } = fieldState;
5712
- const { justSelectedRef, initializedRef } = fieldState;
5713
- const fieldValue = field.state.value;
5714
- if (!initializedRef.current && fieldValue && fieldValue !== inputValue) {
5715
- initializedRef.current = true;
5716
- setInputValue(fieldValue);
5717
- }
5718
- const handleSelect = (suggestion) => {
5719
- const cityName = suggestion.data?.city || suggestion.data?.settlement || suggestion.value;
5720
- justSelectedRef.current = true;
5721
- setInputValue(cityName);
5722
- setIsOpen(false);
5723
- setSuggestions([]);
5724
- field.handleChange(cityName);
5725
- };
5726
- const handleKeyDown = (e) => {
5727
- if (!isOpen || suggestions.length === 0) {
5728
- return;
5729
- }
5730
- switch (e.key) {
5731
- case "ArrowDown":
5732
- e.preventDefault();
5733
- setHighlightedIndex(highlightedIndex < suggestions.length - 1 ? highlightedIndex + 1 : 0);
5734
- break;
5735
- case "ArrowUp":
5736
- e.preventDefault();
5737
- setHighlightedIndex(highlightedIndex > 0 ? highlightedIndex - 1 : suggestions.length - 1);
5738
- break;
5739
- case "Enter":
5740
- e.preventDefault();
5741
- if (highlightedIndex >= 0) {
5742
- handleSelect(suggestions[highlightedIndex]);
5743
- }
5744
- break;
5745
- case "Escape":
5746
- setIsOpen(false);
5747
- break;
5748
- }
5749
- };
5750
- return /* @__PURE__ */ jsxs(
5751
- Field.Root,
5752
- {
5753
- invalid: hasError,
5754
- required: resolved.required,
5755
- disabled: resolved.disabled,
5756
- readOnly: resolved.readOnly,
5757
- children: [
5758
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
5759
- /* @__PURE__ */ jsxs(Box, { ref: containerRef, position: "relative", width: "100%", children: [
5760
- /* @__PURE__ */ jsx(
5761
- Input,
5762
- {
5763
- value: inputValue,
5764
- onChange: (e) => {
5765
- setInputValue(e.target.value);
5766
- setHighlightedIndex(-1);
5767
- if (!e.target.value) {
5768
- field.handleChange("");
5769
- }
5770
- },
5771
- onFocus: () => {
5772
- if (suggestions.length > 0) {
5773
- setIsOpen(true);
5774
- }
5775
- },
5776
- onBlur: () => {
5777
- if (inputValue && inputValue !== field.state.value) {
5778
- field.handleChange(inputValue);
5779
- }
5780
- field.handleBlur();
5781
- },
5782
- onKeyDown: handleKeyDown,
5783
- placeholder: resolved.placeholder ?? "Enter city",
5784
- "data-field-name": fullPath
5785
- }
5786
- ),
5787
- isLoading && /* @__PURE__ */ jsx(Box, { position: "absolute", right: 3, top: "50%", transform: "translateY(-50%)", children: /* @__PURE__ */ jsx(Spinner, { size: "sm" }) }),
5788
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
5789
- List.Root,
5790
- {
5791
- position: "absolute",
5792
- zIndex: 10,
5793
- width: "100%",
5794
- bg: "bg.panel",
5795
- borderWidth: "1px",
5796
- borderRadius: "md",
5797
- shadow: "md",
5798
- maxH: "250px",
5799
- overflowY: "auto",
5800
- mt: 1,
5801
- listStyle: "none",
5802
- children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
5803
- List.Item,
5804
- {
5805
- px: 3,
5806
- py: 2,
5807
- cursor: "pointer",
5808
- bg: highlightedIndex === index ? "bg.muted" : void 0,
5809
- _hover: { bg: "bg.muted" },
5810
- onClick: () => handleSelect(suggestion),
5811
- onMouseEnter: () => setHighlightedIndex(index),
5812
- children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: suggestion.label })
5813
- },
5814
- `${suggestion.value}-${index}`
5815
- ))
5816
- }
5817
- )
5818
- ] }),
5819
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
5820
- ]
5821
- }
5822
- );
5823
- }
5824
- });
5825
1367
  function useIsDarkMode() {
5826
1368
  const [isDark, setIsDark] = useState(false);
5827
1369
  useEffect(() => {
@@ -6903,6 +2445,7 @@ function useStepPersistence(currentStep, config) {
6903
2445
  }
6904
2446
  function useStepState() {
6905
2447
  const [steps, setSteps] = useState([]);
2448
+ const claimedIndicesRef = useRef(/* @__PURE__ */ new Set());
6906
2449
  const [hiddenFields, setHiddenFields] = useState(/* @__PURE__ */ new Set());
6907
2450
  const sortedSteps = useMemo(() => [...steps].sort((a, b) => a.index - b.index), [steps]);
6908
2451
  const stepCount = sortedSteps.length;
@@ -6922,6 +2465,7 @@ function useStepState() {
6922
2465
  });
6923
2466
  }, []);
6924
2467
  const unregisterStep = useCallback((index) => {
2468
+ claimedIndicesRef.current.delete(index);
6925
2469
  setSteps((prev) => prev.filter((s) => s.index !== index));
6926
2470
  }, []);
6927
2471
  const hideFieldsFromValidation = useCallback((fieldNames) => {
@@ -6947,6 +2491,7 @@ function useStepState() {
6947
2491
  stepCount,
6948
2492
  registerStep,
6949
2493
  unregisterStep,
2494
+ claimedIndicesRef,
6950
2495
  hiddenFields,
6951
2496
  hideFieldsFromValidation,
6952
2497
  showFieldsForValidation
@@ -6980,6 +2525,7 @@ function FormSteps({
6980
2525
  stepCount,
6981
2526
  registerStep,
6982
2527
  unregisterStep,
2528
+ claimedIndicesRef,
6983
2529
  hiddenFields,
6984
2530
  hideFieldsFromValidation,
6985
2531
  showFieldsForValidation
@@ -7023,6 +2569,7 @@ function FormSteps({
7023
2569
  isFirstStep: currentStep === 0,
7024
2570
  registerStep,
7025
2571
  unregisterStep,
2572
+ claimedIndicesRef,
7026
2573
  validateOnNext,
7027
2574
  linear,
7028
2575
  orientation,
@@ -7303,7 +2850,7 @@ function FormStepsStep({
7303
2850
  segment
7304
2851
  }) {
7305
2852
  const { form } = useDeclarativeForm();
7306
- const { registerStep, unregisterStep, steps, currentStep, animated, animationDuration, direction } = useFormStepsContext();
2853
+ const { registerStep, unregisterStep, claimedIndicesRef, currentStep, animated, animationDuration, direction } = useFormStepsContext();
7307
2854
  const wrappedChildren = segment ? /* @__PURE__ */ jsx(FormGroupDeclarative, { name: segment, children }) : children;
7308
2855
  const fieldExtractionPath = segment ?? "";
7309
2856
  const [isVisible, setIsVisible] = useState(() => {
@@ -7329,8 +2876,6 @@ function FormStepsStep({
7329
2876
  });
7330
2877
  return () => subscription.unsubscribe();
7331
2878
  }, [form, when]);
7332
- const stepsRef = useRef(steps);
7333
- stepsRef.current = steps;
7334
2879
  useEffect(() => {
7335
2880
  if (!isVisible) {
7336
2881
  if (indexRef.current >= 0) {
@@ -7339,13 +2884,14 @@ function FormStepsStep({
7339
2884
  }
7340
2885
  return;
7341
2886
  }
7342
- const existingIndices = stepsRef.current.map((s) => s.index);
7343
- let nextIndex = 0;
7344
- while (existingIndices.includes(nextIndex)) {
7345
- nextIndex++;
7346
- }
7347
2887
  if (indexRef.current < 0) {
2888
+ const claimed = claimedIndicesRef.current;
2889
+ let nextIndex = 0;
2890
+ while (claimed.has(nextIndex)) {
2891
+ nextIndex++;
2892
+ }
7348
2893
  indexRef.current = nextIndex;
2894
+ claimed.add(nextIndex);
7349
2895
  }
7350
2896
  const fieldNames = extractFieldNames(children, fieldExtractionPath);
7351
2897
  const stepInfo = {
@@ -7361,9 +2907,20 @@ function FormStepsStep({
7361
2907
  return () => {
7362
2908
  if (indexRef.current >= 0) {
7363
2909
  unregisterStep(indexRef.current);
2910
+ indexRef.current = -1;
7364
2911
  }
7365
2912
  };
7366
- }, [description, registerStep, title, unregisterStep, onEnter, onLeave, isVisible, fieldExtractionPath]);
2913
+ }, [
2914
+ description,
2915
+ registerStep,
2916
+ title,
2917
+ unregisterStep,
2918
+ onEnter,
2919
+ onLeave,
2920
+ isVisible,
2921
+ fieldExtractionPath,
2922
+ claimedIndicesRef
2923
+ ]);
7367
2924
  const fieldNamesRef = useRef([]);
7368
2925
  const currentFieldNames = useMemo(
7369
2926
  () => extractFieldNames(children, fieldExtractionPath),
@@ -8001,6 +3558,6 @@ function createNamedGroupContext(contextName) {
8001
3558
  return createSafeContext(contextName);
8002
3559
  }
8003
3560
 
8004
- export { ButtonSubmit, ChakraFormField, DeclarativeFormContext, FieldCombobox, FieldLabel, FieldListbox, FieldNumber, FieldSegmentedGroup, FieldSelect, FieldString, FieldTooltip, Form2 as Form, FormField, FormGroup, FormGroupDeclarative, FormGroupList, FormGroupListDeclarative, FormGroupListItem, RelationFieldProvider, TanStackFormField, booleanMeta, commonMeta, createForm, createNamedGroupContext, createSafeContext, dateMeta, enumMeta, fieldContext, formContext, numberMeta, relationMeta, textMeta, useAppForm, useAsyncSearch, useDebounce, useDeclarativeField, useDeclarativeForm, useDeclarativeFormOptional, useFieldActions, useFieldContext, useFormApi, useFormContext, useFormField, useFormGroup, useFormGroupList, useFormGroupListItem, useFormStepsContext, useRelationFieldContext, useRelationOptions, useTanStackFormField, useTypedFormContext, useTypedFormSubscribe, withForm, withRelations, withUIMeta, withUIMetaDeep };
3561
+ export { ButtonSubmit, ChakraFormField, Form2 as Form, FormField, FormGroupDeclarative, FormGroupList, FormGroupListDeclarative, FormGroupListItem, RelationFieldProvider, TanStackFormField, booleanMeta, commonMeta, createForm, createNamedGroupContext, createSafeContext, dateMeta, enumMeta, fieldContext, formContext, numberMeta, relationMeta, textMeta, useAppForm, useFieldActions, useFieldContext, useFormApi, useFormContext, useFormField, useFormGroupList, useFormGroupListItem, useFormStepsContext, useRelationFieldContext, useRelationOptions, useTanStackFormField, useTypedFormContext, useTypedFormSubscribe, withForm, withRelations, withUIMeta, withUIMetaDeep };
8005
3562
  //# sourceMappingURL=index.js.map
8006
3563
  //# sourceMappingURL=index.js.map