@shwfed/nuxt 0.11.37 → 0.11.38

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.
@@ -15,7 +15,7 @@ import { mergeDslContexts, useCELContext } from "../../../plugins/cel/context";
15
15
  import { Calendar } from "../calendar";
16
16
  import { Button } from "../button";
17
17
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
18
- import { Field, FieldContent, FieldError, FieldLabel } from "../field";
18
+ import { Field, FieldContent, FieldError, FieldLabel, FieldLegend, FieldSet } from "../field";
19
19
  import FieldsConfiguratorDialog from "../fields-configurator/FieldsConfiguratorDialog.vue";
20
20
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupNumberField, InputGroupTextarea } from "../input-group";
21
21
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../popover";
@@ -25,7 +25,7 @@ import { getLocalizedText } from "../../../utils/coders";
25
25
  import { FieldsConfigC, createFieldsConfig } from "./schema";
26
26
  const id = useId();
27
27
  const defaultConfig = createFieldsConfig({
28
- fields: []
28
+ groups: []
29
29
  });
30
30
  const props = defineProps({
31
31
  config: { type: null, required: true }
@@ -53,7 +53,10 @@ function cloneConfig(config2) {
53
53
  const nextConfig = {
54
54
  kind: config2.kind,
55
55
  compatibilityDate: config2.compatibilityDate,
56
- fields: config2.fields.slice()
56
+ groups: config2.groups.map((group) => ({
57
+ ...group,
58
+ fields: group.fields.slice()
59
+ }))
57
60
  };
58
61
  if (config2.orientation) {
59
62
  nextConfig.orientation = config2.orientation;
@@ -129,6 +132,30 @@ function getFieldPartStyle(styleExpression) {
129
132
  function getFieldStyle(field) {
130
133
  return getFieldPartStyle(field.style);
131
134
  }
135
+ function getGroupStyle(group, config2) {
136
+ const style = usesContentsOrientation(config2) ? getConfigStyle(config2) : {};
137
+ const groupStyle = group.style ? evaluateExpression(group.style, { form: modelValue.value, id: group.id }, {}) : {};
138
+ if (!groupStyle || typeof groupStyle !== "object" || Array.isArray(groupStyle)) {
139
+ return style;
140
+ }
141
+ for (const [key, value] of Object.entries(groupStyle)) {
142
+ if (typeof value === "string" || typeof value === "number") {
143
+ Reflect.set(style, key, value);
144
+ }
145
+ }
146
+ return style;
147
+ }
148
+ function getGroupTitle(group) {
149
+ const title = getLocalizedText(group.title, locale.value);
150
+ if (!title) {
151
+ return void 0;
152
+ }
153
+ const normalizedTitle = title.trim();
154
+ return normalizedTitle.length > 0 ? normalizedTitle : void 0;
155
+ }
156
+ function getConfigFields(config2) {
157
+ return config2.groups.flatMap((group) => group.fields);
158
+ }
132
159
  function getFieldContainerStyle(field, config2) {
133
160
  if (!isPassiveField(field) && usesContentsOrientation(config2)) {
134
161
  return {};
@@ -150,6 +177,12 @@ function getFieldContentStyle(field, config2) {
150
177
  function isPassiveField(field) {
151
178
  return field.type === "slot" || field.type === "empty";
152
179
  }
180
+ function isLabeledDisplayField(field) {
181
+ return field.type === "markdown";
182
+ }
183
+ function isInteractiveField(field) {
184
+ return !isLabeledDisplayField(field);
185
+ }
153
186
  function toCalendarDateValue(value, valueFormat) {
154
187
  if (typeof value !== "string") return void 0;
155
188
  const date = parse(value, valueFormat, /* @__PURE__ */ new Date());
@@ -173,8 +206,8 @@ function getFieldValue(field) {
173
206
  return getProperty(modelValue.value, field.path);
174
207
  }
175
208
  function initializeFieldValues(config2) {
176
- for (const field of config2.fields) {
177
- if (isPassiveField(field) || !field.initialValue) {
209
+ for (const field of getConfigFields(config2)) {
210
+ if (isPassiveField(field) || isLabeledDisplayField(field) || !field.initialValue) {
178
211
  continue;
179
212
  }
180
213
  const initialValueResult = tryEvaluateExpression(field.initialValue, { form: modelValue.value });
@@ -492,8 +525,8 @@ function validateField(field) {
492
525
  }
493
526
  function validateFields() {
494
527
  const nextValidationErrors = {};
495
- for (const field of displayConfig.value.fields) {
496
- if (isPassiveField(field)) {
528
+ for (const field of getConfigFields(displayConfig.value)) {
529
+ if (isPassiveField(field) || isLabeledDisplayField(field)) {
497
530
  continue;
498
531
  }
499
532
  const failure = getValidationFailure(field);
@@ -510,6 +543,9 @@ function isFieldInvalid(field) {
510
543
  function getFieldLabel(field) {
511
544
  return getLocalizedText(field.title, locale.value) ?? field.path;
512
545
  }
546
+ function getDisplayFieldLabel(field) {
547
+ return getLocalizedText(field.title, locale.value) ?? field.id;
548
+ }
513
549
  function isFieldRequired(field) {
514
550
  return field.required === true;
515
551
  }
@@ -518,6 +554,13 @@ function renderValidationMessage(field) {
518
554
  if (!error) return "";
519
555
  return $md.inline`${error.message}`(mergeDslContexts(error.context, dslContext));
520
556
  }
557
+ function renderMarkdownField(field) {
558
+ const source = getLocalizedText(field.locale, locale.value) ?? "";
559
+ if (source.trim().length === 0) {
560
+ return "";
561
+ }
562
+ return $md.block`${source}`(mergeDslContexts({ form: modelValue.value }, dslContext));
563
+ }
521
564
  function isCalendarDateDisabled(field, date) {
522
565
  if (!field.disableDate) {
523
566
  return false;
@@ -574,8 +617,8 @@ watch(config, (value) => {
574
617
  }, { immediate: true });
575
618
  watchEffect(() => {
576
619
  const activePaths = /* @__PURE__ */ new Set();
577
- for (const field of displayConfig.value.fields) {
578
- if (!isPassiveField(field) && !isFieldHidden(field) && !isFieldDisabled(field)) {
620
+ for (const field of getConfigFields(displayConfig.value)) {
621
+ if (!isPassiveField(field) && !isFieldHidden(field) && !isLabeledDisplayField(field) && !isFieldDisabled(field)) {
579
622
  activePaths.add(field.path);
580
623
  }
581
624
  }
@@ -603,11 +646,13 @@ export {
603
646
  CURRENT_COMPATIBILITY_DATE,
604
647
  EmptyFieldC,
605
648
  FieldC,
649
+ FieldGroupC,
606
650
  FieldsBodyC,
607
651
  FieldsBodyInputC,
608
652
  FieldsConfigC,
609
653
  FieldsConfigInputC,
610
654
  KIND,
655
+ MarkdownFieldC,
611
656
  NumberFieldC,
612
657
  SelectFieldC,
613
658
  SlotFieldC,
@@ -625,7 +670,7 @@ export {
625
670
  :class="[
626
671
  'relative p-1 -m-1 border border-dashed',
627
672
  isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent',
628
- isConfigBordered(displayConfig) ? '!p-0 !m-0 border-solid border-zinc-200' : ''
673
+ isConfigBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
629
674
  ]"
630
675
  :style="getConfigStyle(displayConfig)"
631
676
  >
@@ -653,176 +698,455 @@ export {
653
698
  class="absolute inset-0 z-10 w-full h-full"
654
699
  />
655
700
 
656
- <template
657
- v-for="field in displayConfig.fields"
658
- :key="field.id"
701
+ <FieldSet
702
+ v-for="group in displayConfig.groups"
703
+ :key="group.id"
704
+ data-slot="fields-group"
705
+ :data-group-id="group.id"
706
+ :class="[
707
+ isConfigBordered(displayConfig) ? 'overflow-hidden rounded-md border border-zinc-200' : ''
708
+ ]"
709
+ :style="getGroupStyle(group, displayConfig)"
659
710
  >
660
- <div
661
- v-if="field.type === 'slot' && isConfigBordered(displayConfig)"
662
- :class="'border-b border-r border-zinc-200'"
663
- :style="getFieldStyle(field)"
711
+ <FieldLegend
712
+ v-if="getGroupTitle(group)"
713
+ data-slot="fields-group-legend"
714
+ class="px-2 pt-2"
715
+ >
716
+ {{ getGroupTitle(group) }}
717
+ </FieldLegend>
718
+
719
+ <template
720
+ v-for="field in group.fields"
721
+ :key="field.id"
664
722
  >
723
+ <div
724
+ v-if="field.type === 'slot' && isConfigBordered(displayConfig)"
725
+ :class="'border-b border-r border-zinc-200'"
726
+ :style="getFieldStyle(field)"
727
+ >
728
+ <slot
729
+ :name="field.id"
730
+ :form="slotForm"
731
+ :style="{}"
732
+ :valid="valid"
733
+ />
734
+ </div>
665
735
  <slot
736
+ v-else-if="field.type === 'slot'"
666
737
  :name="field.id"
667
738
  :form="slotForm"
668
- :style="{}"
739
+ :style="getFieldStyle(field)"
669
740
  :valid="valid"
670
741
  />
671
- </div>
672
- <slot
673
- v-else-if="field.type === 'slot'"
674
- :name="field.id"
675
- :form="slotForm"
676
- :style="getFieldStyle(field)"
677
- :valid="valid"
678
- />
679
- <div
680
- v-else-if="field.type === 'empty'"
681
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
682
- :style="getFieldStyle(field)"
683
- />
684
- <div
685
- v-else-if="field.type === 'upload' && !isFieldHidden(field)"
686
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
687
- :style="getFieldContainerStyle(field, displayConfig)"
688
- class="flex flex-col gap-2 p-2"
689
- >
690
- <p class="text-sm font-medium text-zinc-700">
691
- <span class="inline-flex items-start gap-0.5">
692
- <span>{{ getFieldLabel(field) }}</span>
693
- <sup
694
- v-if="isFieldRequired(field)"
695
- class="text-red-500 leading-none"
696
- >*</sup>
697
- </span>
698
- </p>
699
- <label
700
- v-if="getUploadFiles(field).length < getUploadMaxCount(field)"
701
- :class="[
702
- 'flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-8 transition-colors',
703
- isFieldDisabled(field) ? 'cursor-not-allowed border-zinc-200 opacity-50' : 'border-zinc-300 hover:border-zinc-400 hover:bg-zinc-50'
704
- ]"
705
- @drop="handleUploadDrop(field, $event)"
706
- @dragover="handleUploadDragOver"
742
+ <div
743
+ v-else-if="field.type === 'empty'"
744
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
745
+ :style="getFieldStyle(field)"
746
+ />
747
+ <Field
748
+ v-else-if="field.type === 'markdown' && !isFieldHidden(field)"
749
+ :orientation="getConfigOrientation(displayConfig)"
750
+ :style="getFieldContainerStyle(field, displayConfig)"
707
751
  >
708
- <Icon
709
- :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
710
- class="text-4xl text-zinc-400"
711
- />
712
- <button
713
- v-if="field.template"
714
- type="button"
715
- class="inline-flex items-center gap-1.5 rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm text-zinc-600 transition-colors hover:border-[--el-color-primary] hover:text-[--el-color-primary] disabled:opacity-50"
716
- :disabled="templateDownloading[field.id]"
717
- @click.prevent.stop="handleTemplateDownload(field)"
752
+ <FieldLabel
753
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
754
+ :style="getFieldLabelStyle(field, displayConfig)"
718
755
  >
719
- <Icon
720
- :icon="templateDownloading[field.id] ? 'svg-spinners:ring-resize' : getUploadTemplateIcon(field)"
721
- class="text-base"
722
- />
723
- {{ getLocalizedText(field.templateName, locale) ?? t("upload-download-template") }}
724
- </button>
725
- <p
726
- v-if="field.description"
727
- class="text-sm text-zinc-600"
728
- v-html="$md.inline`${getLocalizedText(field.description, locale) ?? ''}`()"
729
- />
730
- <p
731
- v-else
732
- class="text-sm text-zinc-600"
756
+ <span>{{ getDisplayFieldLabel(field) }}</span>
757
+ </FieldLabel>
758
+ <FieldContent
759
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
760
+ :style="getFieldContentStyle(field, displayConfig)"
733
761
  >
734
- {{ t("upload-click-or-drag") }}
735
- </p>
736
- <p class="text-xs text-zinc-400">
737
- {{ getUploadDescription(field) || t("upload-accept-all") }}
738
- </p>
739
- <input
740
- type="file"
741
- class="sr-only"
742
- :accept="getUploadAcceptString(field)"
743
- :disabled="isFieldDisabled(field)"
744
- :multiple="getUploadMaxCount(field) !== 1"
745
- @change="handleUploadInputChange(field, $event)"
746
- >
747
- </label>
748
- <ul
749
- v-if="getUploadFiles(field).length > 0"
750
- class="flex flex-col gap-1"
762
+ <div
763
+ data-slot="fields-markdown"
764
+ v-html="renderMarkdownField(field)"
765
+ />
766
+ </FieldContent>
767
+ </Field>
768
+ <div
769
+ v-else-if="field.type === 'upload' && !isFieldHidden(field)"
770
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
771
+ :style="getFieldContainerStyle(field, displayConfig)"
772
+ class="flex flex-col gap-2 p-2"
751
773
  >
752
- <li
753
- v-for="(file, fileIndex) in getUploadFiles(field)"
754
- :key="`${field.id}:${fileIndex}:${file.name}`"
755
- class="flex items-center gap-2 rounded-md border border-zinc-200 px-3 py-2"
774
+ <p class="text-sm font-medium text-zinc-700">
775
+ <span class="inline-flex items-start gap-0.5">
776
+ <span>{{ getFieldLabel(field) }}</span>
777
+ <sup
778
+ v-if="isFieldRequired(field)"
779
+ class="text-red-500 leading-none"
780
+ >*</sup>
781
+ </span>
782
+ </p>
783
+ <label
784
+ v-if="getUploadFiles(field).length < getUploadMaxCount(field)"
785
+ :class="[
786
+ 'flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-8 transition-colors',
787
+ isFieldDisabled(field) ? 'cursor-not-allowed border-zinc-200 opacity-50' : 'border-zinc-300 hover:border-zinc-400 hover:bg-zinc-50'
788
+ ]"
789
+ @drop="handleUploadDrop(field, $event)"
790
+ @dragover="handleUploadDragOver"
756
791
  >
757
792
  <Icon
758
- :icon="getFileIcon(file.name)"
759
- class="shrink-0 text-lg"
793
+ :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
794
+ class="text-4xl text-zinc-400"
760
795
  />
761
- <span class="flex-1 truncate text-sm text-zinc-700">{{ file.name }}</span>
762
796
  <button
797
+ v-if="field.template"
763
798
  type="button"
764
- class="shrink-0 text-zinc-300 transition-colors hover:text-red-500"
765
- :disabled="isFieldDisabled(field)"
766
- @click="removeUploadFile(field, fileIndex)"
799
+ class="inline-flex items-center gap-1.5 rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm text-zinc-600 transition-colors hover:border-[--el-color-primary] hover:text-[--el-color-primary] disabled:opacity-50"
800
+ :disabled="templateDownloading[field.id]"
801
+ @click.prevent.stop="handleTemplateDownload(field)"
767
802
  >
768
803
  <Icon
769
- icon="fluent:delete-20-regular"
770
- class="text-lg"
804
+ :icon="templateDownloading[field.id] ? 'svg-spinners:ring-resize' : getUploadTemplateIcon(field)"
805
+ class="text-base"
771
806
  />
807
+ {{ getLocalizedText(field.templateName, locale) ?? t("upload-download-template") }}
772
808
  </button>
773
- </li>
774
- </ul>
775
- </div>
776
- <Field
777
- v-else-if="!isFieldHidden(field)"
778
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
779
- :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
780
- :orientation="getConfigOrientation(displayConfig)"
781
- :style="getFieldContainerStyle(field, displayConfig)"
782
- >
783
- <FieldLabel
784
- :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0"
785
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
786
- :style="getFieldLabelStyle(field, displayConfig)"
787
- >
788
- <span class="inline-flex items-start gap-0.5">
789
- <span>{{ getFieldLabel(field) }}</span>
790
- <sup
791
- v-if="isFieldRequired(field)"
792
- class="text-red-500 leading-none"
793
- >*</sup>
794
- </span>
795
- <span v-if="isCheating">
796
- <span class="font-mono">{{ field.path }}</span>
797
- </span>
798
- </FieldLabel>
799
- <FieldContent
800
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
801
- :style="getFieldContentStyle(field, displayConfig)"
809
+ <p
810
+ v-if="field.description"
811
+ class="text-sm text-zinc-600"
812
+ v-html="$md.inline`${getLocalizedText(field.description, locale) ?? ''}`()"
813
+ />
814
+ <p
815
+ v-else
816
+ class="text-sm text-zinc-600"
817
+ >
818
+ {{ t("upload-click-or-drag") }}
819
+ </p>
820
+ <p class="text-xs text-zinc-400">
821
+ {{ getUploadDescription(field) || t("upload-accept-all") }}
822
+ </p>
823
+ <input
824
+ type="file"
825
+ class="sr-only"
826
+ :accept="getUploadAcceptString(field)"
827
+ :disabled="isFieldDisabled(field)"
828
+ :multiple="getUploadMaxCount(field) !== 1"
829
+ @change="handleUploadInputChange(field, $event)"
830
+ >
831
+ </label>
832
+ <ul
833
+ v-if="getUploadFiles(field).length > 0"
834
+ class="flex flex-col gap-1"
835
+ >
836
+ <li
837
+ v-for="(file, fileIndex) in getUploadFiles(field)"
838
+ :key="`${field.id}:${fileIndex}:${file.name}`"
839
+ class="flex items-center gap-2 rounded-md border border-zinc-200 px-3 py-2"
840
+ >
841
+ <Icon
842
+ :icon="getFileIcon(file.name)"
843
+ class="shrink-0 text-lg"
844
+ />
845
+ <span class="flex-1 truncate text-sm text-zinc-700">{{ file.name }}</span>
846
+ <button
847
+ type="button"
848
+ class="shrink-0 text-zinc-300 transition-colors hover:text-red-500"
849
+ :disabled="isFieldDisabled(field)"
850
+ @click="removeUploadFile(field, fileIndex)"
851
+ >
852
+ <Icon
853
+ icon="fluent:delete-20-regular"
854
+ class="text-lg"
855
+ />
856
+ </button>
857
+ </li>
858
+ </ul>
859
+ </div>
860
+ <Field
861
+ v-else-if="isInteractiveField(field) && !isFieldHidden(field)"
862
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
863
+ :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
864
+ :orientation="getConfigOrientation(displayConfig)"
865
+ :style="getFieldContainerStyle(field, displayConfig)"
802
866
  >
803
- <Popover
804
- v-if="field.type === 'calendar'"
805
- @update:open="(open) => handleCalendarOpenChange(field, open)"
867
+ <FieldLabel
868
+ :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0"
869
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
870
+ :style="getFieldLabelStyle(field, displayConfig)"
871
+ >
872
+ <span class="inline-flex items-start gap-0.5">
873
+ <span>{{ getFieldLabel(field) }}</span>
874
+ <sup
875
+ v-if="isFieldRequired(field)"
876
+ class="text-red-500 leading-none"
877
+ >*</sup>
878
+ </span>
879
+ <span v-if="isCheating">
880
+ <span class="font-mono">{{ field.path }}</span>
881
+ </span>
882
+ </FieldLabel>
883
+ <FieldContent
884
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
885
+ :style="getFieldContentStyle(field, displayConfig)"
806
886
  >
807
- <PopoverAnchor as-child>
808
- <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
809
- <PopoverTrigger as-child>
810
- <InputGroupInput
811
- :model-value="displayCalendarValue(getProperty(modelValue, field.path), field.display, field.value)"
812
- class="text-left"
887
+ <Popover
888
+ v-if="field.type === 'calendar'"
889
+ @update:open="(open) => handleCalendarOpenChange(field, open)"
890
+ >
891
+ <PopoverAnchor as-child>
892
+ <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
893
+ <PopoverTrigger as-child>
894
+ <InputGroupInput
895
+ :model-value="displayCalendarValue(getProperty(modelValue, field.path), field.display, field.value)"
896
+ class="text-left"
897
+ :disabled="isFieldDisabled(field)"
898
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
899
+ readonly
900
+ @blur="handleCalendarBlur(field)"
901
+ />
902
+ </PopoverTrigger>
903
+ <InputGroupAddon v-if="field.icon">
904
+ <Icon
905
+ :icon="field.icon"
906
+ />
907
+ </InputGroupAddon>
908
+ <InputGroupAddon
909
+ v-if="hasProperty(modelValue, field.path)"
910
+ align="inline-end"
911
+ :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
912
+ >
913
+ <Tooltip :delay-duration="800">
914
+ <TooltipTrigger>
915
+ <InputGroupButton as-child>
916
+ <button
917
+ type="button"
918
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
919
+ :disabled="isFieldDisabled(field)"
920
+ @click="deleteProperty(modelValue, field.path)"
921
+ >
922
+ <Icon
923
+ icon="fluent:dismiss-20-regular"
924
+ />
925
+ </button>
926
+ </InputGroupButton>
927
+ </TooltipTrigger>
928
+ <TooltipContent>
929
+ {{ t("clear") }}
930
+ </TooltipContent>
931
+ </Tooltip>
932
+ </InputGroupAddon>
933
+ </InputGroup>
934
+ </PopoverAnchor>
935
+ <PopoverContent class="w-72">
936
+ <Calendar
937
+ :locale="locale"
938
+ :layout="field.mode"
939
+ :model-value="toCalendarDateValue(getProperty(modelValue, field.path), field.value)"
940
+ :disabled="isFieldDisabled(field)"
941
+ :is-date-disabled="field.disableDate ? (date) => isCalendarDateDisabled(field, date) : void 0"
942
+ @update:model-value="(value) => {
943
+ if (value === void 0) {
944
+ deleteProperty(modelValue, field.path);
945
+ } else {
946
+ setProperty(modelValue, field.path, format(value.toDate(getLocalTimeZone()), field.value));
947
+ }
948
+ }"
949
+ />
950
+ </PopoverContent>
951
+ </Popover>
952
+ <template v-else>
953
+ <template v-if="field.type === 'select'">
954
+ <Popover
955
+ v-for="selectState in [getSelectFieldState(field)]"
956
+ :key="`${field.id}:select:${selectState.selectedKey ?? 'empty'}`"
957
+ :open="selectOpen[field.path] === true"
958
+ @update:open="(open) => handleSelectOpenChange(field, open)"
959
+ >
960
+ <PopoverAnchor as-child>
961
+ <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
962
+ <PopoverTrigger as-child>
963
+ <InputGroupInput
964
+ :id="`${id}:${field.path}`"
965
+ :model-value="getSelectDisplayValue(selectState, selectState.selectedKey)"
966
+ :disabled="isFieldDisabled(field)"
967
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
968
+ :placeholder="t('select-placeholder')"
969
+ class="text-left"
970
+ readonly
971
+ @blur="handleSelectBlur(field)"
972
+ />
973
+ </PopoverTrigger>
974
+ <InputGroupAddon v-if="field.icon">
975
+ <Icon
976
+ :icon="field.icon"
977
+ />
978
+ </InputGroupAddon>
979
+ <InputGroupAddon
980
+ v-if="hasProperty(modelValue, field.path)"
981
+ align="inline-end"
982
+ :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
983
+ >
984
+ <Tooltip :delay-duration="800">
985
+ <TooltipTrigger>
986
+ <InputGroupButton as-child>
987
+ <button
988
+ type="button"
989
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
990
+ :disabled="isFieldDisabled(field)"
991
+ @click="clearSelectField(field)"
992
+ >
993
+ <Icon
994
+ icon="fluent:dismiss-20-regular"
995
+ />
996
+ </button>
997
+ </InputGroupButton>
998
+ </TooltipTrigger>
999
+ <TooltipContent>
1000
+ {{ t("clear") }}
1001
+ </TooltipContent>
1002
+ </Tooltip>
1003
+ </InputGroupAddon>
1004
+ </InputGroup>
1005
+ </PopoverAnchor>
1006
+
1007
+ <PopoverContent
1008
+ class="p-0"
1009
+ :style="{ width: 'var(--reka-popover-trigger-width)' }"
1010
+ >
1011
+ <Command
1012
+ :model-value="selectState.selectedKey"
1013
+ :disabled="isFieldDisabled(field)"
1014
+ selection-behavior="toggle"
1015
+ @update:model-value="(value) => handleSelectCommandValueChange(field, selectState, value)"
1016
+ >
1017
+ <CommandInput :placeholder="t('select-search-placeholder')" />
1018
+ <CommandList>
1019
+ <CommandEmpty as-child>
1020
+ <section class="h-32 flex flex-col text-lg items-center justify-center gap-2 select-none">
1021
+ <Icon
1022
+ icon="fluent:app-recent-20-regular"
1023
+ class="text-zinc-400 text-2xl!"
1024
+ />
1025
+ <p class="text-zinc-500">
1026
+ {{ t("select-empty") }}
1027
+ </p>
1028
+ </section>
1029
+ </CommandEmpty>
1030
+ <CommandGroup>
1031
+ <CommandItem
1032
+ v-for="option in selectState.options"
1033
+ :key="option.key"
1034
+ data-slot="select-option"
1035
+ :value="option.key"
1036
+ class="data-highlighted:bg-zinc-50 data-highlighted:text-zinc-700 data-[state=checked]:bg-zinc-100 data-[state=checked]:text-zinc-700 transition cursor-pointer relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none"
1037
+ >
1038
+ {{ option.label }}
1039
+ </CommandItem>
1040
+ </CommandGroup>
1041
+ </CommandList>
1042
+ </Command>
1043
+ </PopoverContent>
1044
+ </Popover>
1045
+ </template>
1046
+
1047
+ <template v-else-if="field.type === 'radio'">
1048
+ <RadioGroupRoot
1049
+ v-for="radioState in [getSelectFieldState(field)]"
1050
+ :key="`${field.id}:radio:${radioState.selectedKey ?? 'empty'}`"
1051
+ :model-value="radioState.selectedKey"
1052
+ :disabled="isFieldDisabled(field)"
1053
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1054
+ class="flex flex-wrap gap-x-4 gap-y-1.5"
1055
+ @update:model-value="(value) => handleSelectValueChange(field, radioState, value)"
1056
+ @focusout="validateField(field)"
1057
+ >
1058
+ <label
1059
+ v-for="option in radioState.options"
1060
+ :key="option.key"
1061
+ class="flex items-center gap-1.5 text-sm cursor-pointer data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
1062
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
1063
+ >
1064
+ <RadioGroupItem
1065
+ :value="option.key"
1066
+ data-slot="radio-group-item"
1067
+ class="size-4 rounded-full border border-zinc-300 data-[state=checked]:border-(--primary) data-[state=checked]:bg-(--primary) focus:outline-none focus-visible:ring-1 focus-visible:ring-(--primary) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
1068
+ >
1069
+ <RadioGroupIndicator class="flex items-center justify-center">
1070
+ <span class="size-1.5 rounded-full bg-white" />
1071
+ </RadioGroupIndicator>
1072
+ </RadioGroupItem>
1073
+ {{ option.label }}
1074
+ </label>
1075
+ </RadioGroupRoot>
1076
+ </template>
1077
+
1078
+ <InputGroup
1079
+ v-else
1080
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
1081
+ :class="field.type === 'textarea' ? 'h-auto flex-col items-stretch' : void 0"
1082
+ >
1083
+ <div
1084
+ v-if="field.type === 'textarea'"
1085
+ class="flex min-w-0 w-full items-center"
1086
+ >
1087
+ <InputGroupTextarea
1088
+ :id="`${id}:${field.path}`"
1089
+ :model-value="getProperty(modelValue, field.path)"
1090
+ :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
813
1091
  :disabled="isFieldDisabled(field)"
814
1092
  :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
815
- readonly
816
- @blur="handleCalendarBlur(field)"
1093
+ @update:model-value="(value) => {
1094
+ if (!value && !field.discardEmptyString) {
1095
+ deleteProperty(modelValue, field.path);
1096
+ } else {
1097
+ setProperty(modelValue, field.path, value);
1098
+ }
1099
+ }"
1100
+ @blur="validateField(field)"
817
1101
  />
818
- </PopoverTrigger>
819
- <InputGroupAddon v-if="field.icon">
1102
+ <InputGroupAddon v-if="field.icon">
1103
+ <Icon
1104
+ :icon="field.icon"
1105
+ />
1106
+ </InputGroupAddon>
1107
+ </div>
1108
+ <InputGroupInput
1109
+ v-if="field.type === 'string'"
1110
+ :id="`${id}:${field.path}`"
1111
+ :treat-empty-as-different-state-from-null="!!field.discardEmptyString"
1112
+ :model-value="getProperty(modelValue, field.path)"
1113
+ :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1114
+ :disabled="isFieldDisabled(field)"
1115
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1116
+ @update:model-value="(value) => {
1117
+ if (!value && !field.discardEmptyString) {
1118
+ deleteProperty(modelValue, field.path);
1119
+ } else {
1120
+ setProperty(modelValue, field.path, value);
1121
+ }
1122
+ }"
1123
+ @blur="validateField(field)"
1124
+ />
1125
+ <InputGroupNumberField
1126
+ v-if="field.type === 'number'"
1127
+ :id="`${id}:${field.path}`"
1128
+ :model-value="getProperty(modelValue, field.path) ?? null"
1129
+ :min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
1130
+ :max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
1131
+ :step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
1132
+ :disabled="isFieldDisabled(field)"
1133
+ :invalid="isFieldInvalid(field)"
1134
+ @update:model-value="(value) => {
1135
+ if (!value && value !== 0) {
1136
+ deleteProperty(modelValue, field.path);
1137
+ } else {
1138
+ setProperty(modelValue, field.path, value);
1139
+ }
1140
+ }"
1141
+ @blur="validateField(field)"
1142
+ />
1143
+ <InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
820
1144
  <Icon
821
1145
  :icon="field.icon"
822
1146
  />
823
1147
  </InputGroupAddon>
824
1148
  <InputGroupAddon
825
- v-if="hasProperty(modelValue, field.path)"
1149
+ v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
826
1150
  align="inline-end"
827
1151
  :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
828
1152
  >
@@ -846,299 +1170,60 @@ export {
846
1170
  </TooltipContent>
847
1171
  </Tooltip>
848
1172
  </InputGroupAddon>
849
- </InputGroup>
850
- </PopoverAnchor>
851
- <PopoverContent class="w-72">
852
- <Calendar
853
- :locale="locale"
854
- :layout="field.mode"
855
- :model-value="toCalendarDateValue(getProperty(modelValue, field.path), field.value)"
856
- :disabled="isFieldDisabled(field)"
857
- :is-date-disabled="field.disableDate ? (date) => isCalendarDateDisabled(field, date) : void 0"
858
- @update:model-value="(value) => {
859
- if (value === void 0) {
860
- deleteProperty(modelValue, field.path);
861
- } else {
862
- setProperty(modelValue, field.path, format(value.toDate(getLocalTimeZone()), field.value));
863
- }
864
- }"
865
- />
866
- </PopoverContent>
867
- </Popover>
868
- <template v-else>
869
- <template v-if="field.type === 'select'">
870
- <Popover
871
- v-for="selectState in [getSelectFieldState(field)]"
872
- :key="`${field.id}:select:${selectState.selectedKey ?? 'empty'}`"
873
- :open="selectOpen[field.path] === true"
874
- @update:open="(open) => handleSelectOpenChange(field, open)"
875
- >
876
- <PopoverAnchor as-child>
877
- <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
878
- <PopoverTrigger as-child>
879
- <InputGroupInput
880
- :id="`${id}:${field.path}`"
881
- :model-value="getSelectDisplayValue(selectState, selectState.selectedKey)"
882
- :disabled="isFieldDisabled(field)"
883
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
884
- :placeholder="t('select-placeholder')"
885
- class="text-left"
886
- readonly
887
- @blur="handleSelectBlur(field)"
888
- />
889
- </PopoverTrigger>
890
- <InputGroupAddon v-if="field.icon">
891
- <Icon
892
- :icon="field.icon"
893
- />
894
- </InputGroupAddon>
895
- <InputGroupAddon
896
- v-if="hasProperty(modelValue, field.path)"
897
- align="inline-end"
898
- :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
899
- >
900
- <Tooltip :delay-duration="800">
901
- <TooltipTrigger>
902
- <InputGroupButton as-child>
903
- <button
904
- type="button"
905
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
906
- :disabled="isFieldDisabled(field)"
907
- @click="clearSelectField(field)"
908
- >
909
- <Icon
910
- icon="fluent:dismiss-20-regular"
911
- />
912
- </button>
913
- </InputGroupButton>
914
- </TooltipTrigger>
915
- <TooltipContent>
916
- {{ t("clear") }}
917
- </TooltipContent>
918
- </Tooltip>
919
- </InputGroupAddon>
920
- </InputGroup>
921
- </PopoverAnchor>
922
-
923
- <PopoverContent
924
- class="p-0"
925
- :style="{ width: 'var(--reka-popover-trigger-width)' }"
1173
+ <InputGroupAddon
1174
+ v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
1175
+ align="inline-end"
926
1176
  >
927
- <Command
928
- :model-value="selectState.selectedKey"
929
- :disabled="isFieldDisabled(field)"
930
- selection-behavior="toggle"
931
- @update:model-value="(value) => handleSelectCommandValueChange(field, selectState, value)"
1177
+ <span class="text-xs text-zinc-400 font-mono">
1178
+ <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1179
+ </span>
1180
+ </InputGroupAddon>
1181
+ <InputGroupAddon
1182
+ v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
1183
+ align="block-end"
1184
+ >
1185
+ <Tooltip
1186
+ v-if="hasProperty(modelValue, field.path)"
1187
+ :delay-duration="800"
932
1188
  >
933
- <CommandInput :placeholder="t('select-search-placeholder')" />
934
- <CommandList>
935
- <CommandEmpty as-child>
936
- <section class="h-32 flex flex-col text-lg items-center justify-center gap-2 select-none">
1189
+ <TooltipTrigger>
1190
+ <InputGroupButton as-child>
1191
+ <button
1192
+ type="button"
1193
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
1194
+ :disabled="isFieldDisabled(field)"
1195
+ @click="deleteProperty(modelValue, field.path)"
1196
+ >
937
1197
  <Icon
938
- icon="fluent:app-recent-20-regular"
939
- class="text-zinc-400 text-2xl!"
1198
+ icon="fluent:dismiss-20-regular"
940
1199
  />
941
- <p class="text-zinc-500">
942
- {{ t("select-empty") }}
943
- </p>
944
- </section>
945
- </CommandEmpty>
946
- <CommandGroup>
947
- <CommandItem
948
- v-for="option in selectState.options"
949
- :key="option.key"
950
- data-slot="select-option"
951
- :value="option.key"
952
- class="data-highlighted:bg-zinc-50 data-highlighted:text-zinc-700 data-[state=checked]:bg-zinc-100 data-[state=checked]:text-zinc-700 transition cursor-pointer relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none"
953
- >
954
- {{ option.label }}
955
- </CommandItem>
956
- </CommandGroup>
957
- </CommandList>
958
- </Command>
959
- </PopoverContent>
960
- </Popover>
961
- </template>
962
-
963
- <template v-else-if="field.type === 'radio'">
964
- <RadioGroupRoot
965
- v-for="radioState in [getSelectFieldState(field)]"
966
- :key="`${field.id}:radio:${radioState.selectedKey ?? 'empty'}`"
967
- :model-value="radioState.selectedKey"
968
- :disabled="isFieldDisabled(field)"
969
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
970
- class="flex flex-wrap gap-x-4 gap-y-1.5"
971
- @update:model-value="(value) => handleSelectValueChange(field, radioState, value)"
972
- @focusout="validateField(field)"
973
- >
974
- <label
975
- v-for="option in radioState.options"
976
- :key="option.key"
977
- class="flex items-center gap-1.5 text-sm cursor-pointer data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
978
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
979
- >
980
- <RadioGroupItem
981
- :value="option.key"
982
- data-slot="radio-group-item"
983
- class="size-4 rounded-full border border-zinc-300 data-[state=checked]:border-(--primary) data-[state=checked]:bg-(--primary) focus:outline-none focus-visible:ring-1 focus-visible:ring-(--primary) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
1200
+ </button>
1201
+ </InputGroupButton>
1202
+ </TooltipTrigger>
1203
+ <TooltipContent>
1204
+ {{ t("clear") }}
1205
+ </TooltipContent>
1206
+ </Tooltip>
1207
+ <span
1208
+ v-if="field.maxLength && getProperty(modelValue, field.path)"
1209
+ class="text-xs text-zinc-400 font-mono"
984
1210
  >
985
- <RadioGroupIndicator class="flex items-center justify-center">
986
- <span class="size-1.5 rounded-full bg-white" />
987
- </RadioGroupIndicator>
988
- </RadioGroupItem>
989
- {{ option.label }}
990
- </label>
991
- </RadioGroupRoot>
1211
+ <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1212
+ </span>
1213
+ </InputGroupAddon>
1214
+ </InputGroup>
992
1215
  </template>
993
1216
 
994
- <InputGroup
995
- v-else
996
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
997
- :class="field.type === 'textarea' ? 'h-auto flex-col items-stretch' : void 0"
1217
+ <FieldError
1218
+ v-if="isFieldInvalid(field)"
1219
+ :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
998
1220
  >
999
- <div
1000
- v-if="field.type === 'textarea'"
1001
- class="flex min-w-0 w-full items-center"
1002
- >
1003
- <InputGroupTextarea
1004
- :id="`${id}:${field.path}`"
1005
- :model-value="getProperty(modelValue, field.path)"
1006
- :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1007
- :disabled="isFieldDisabled(field)"
1008
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1009
- @update:model-value="(value) => {
1010
- if (!value && !field.discardEmptyString) {
1011
- deleteProperty(modelValue, field.path);
1012
- } else {
1013
- setProperty(modelValue, field.path, value);
1014
- }
1015
- }"
1016
- @blur="validateField(field)"
1017
- />
1018
- <InputGroupAddon v-if="field.icon">
1019
- <Icon
1020
- :icon="field.icon"
1021
- />
1022
- </InputGroupAddon>
1023
- </div>
1024
- <InputGroupInput
1025
- v-if="field.type === 'string'"
1026
- :id="`${id}:${field.path}`"
1027
- :treat-empty-as-different-state-from-null="!!field.discardEmptyString"
1028
- :model-value="getProperty(modelValue, field.path)"
1029
- :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1030
- :disabled="isFieldDisabled(field)"
1031
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1032
- @update:model-value="(value) => {
1033
- if (!value && !field.discardEmptyString) {
1034
- deleteProperty(modelValue, field.path);
1035
- } else {
1036
- setProperty(modelValue, field.path, value);
1037
- }
1038
- }"
1039
- @blur="validateField(field)"
1040
- />
1041
- <InputGroupNumberField
1042
- v-if="field.type === 'number'"
1043
- :id="`${id}:${field.path}`"
1044
- :model-value="getProperty(modelValue, field.path) ?? null"
1045
- :min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
1046
- :max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
1047
- :step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
1048
- :disabled="isFieldDisabled(field)"
1049
- :invalid="isFieldInvalid(field)"
1050
- @update:model-value="(value) => {
1051
- if (!value && value !== 0) {
1052
- deleteProperty(modelValue, field.path);
1053
- } else {
1054
- setProperty(modelValue, field.path, value);
1055
- }
1056
- }"
1057
- @blur="validateField(field)"
1058
- />
1059
- <InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
1060
- <Icon
1061
- :icon="field.icon"
1062
- />
1063
- </InputGroupAddon>
1064
- <InputGroupAddon
1065
- v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
1066
- align="inline-end"
1067
- :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
1068
- >
1069
- <Tooltip :delay-duration="800">
1070
- <TooltipTrigger>
1071
- <InputGroupButton as-child>
1072
- <button
1073
- type="button"
1074
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
1075
- :disabled="isFieldDisabled(field)"
1076
- @click="deleteProperty(modelValue, field.path)"
1077
- >
1078
- <Icon
1079
- icon="fluent:dismiss-20-regular"
1080
- />
1081
- </button>
1082
- </InputGroupButton>
1083
- </TooltipTrigger>
1084
- <TooltipContent>
1085
- {{ t("clear") }}
1086
- </TooltipContent>
1087
- </Tooltip>
1088
- </InputGroupAddon>
1089
- <InputGroupAddon
1090
- v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
1091
- align="inline-end"
1092
- >
1093
- <span class="text-xs text-zinc-400 font-mono">
1094
- <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1095
- </span>
1096
- </InputGroupAddon>
1097
- <InputGroupAddon
1098
- v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
1099
- align="block-end"
1100
- >
1101
- <Tooltip
1102
- v-if="hasProperty(modelValue, field.path)"
1103
- :delay-duration="800"
1104
- >
1105
- <TooltipTrigger>
1106
- <InputGroupButton as-child>
1107
- <button
1108
- type="button"
1109
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
1110
- :disabled="isFieldDisabled(field)"
1111
- @click="deleteProperty(modelValue, field.path)"
1112
- >
1113
- <Icon
1114
- icon="fluent:dismiss-20-regular"
1115
- />
1116
- </button>
1117
- </InputGroupButton>
1118
- </TooltipTrigger>
1119
- <TooltipContent>
1120
- {{ t("clear") }}
1121
- </TooltipContent>
1122
- </Tooltip>
1123
- <span
1124
- v-if="field.maxLength && getProperty(modelValue, field.path)"
1125
- class="text-xs text-zinc-400 font-mono"
1126
- >
1127
- <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1128
- </span>
1129
- </InputGroupAddon>
1130
- </InputGroup>
1131
- </template>
1132
-
1133
- <FieldError
1134
- v-if="isFieldInvalid(field)"
1135
- :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
1136
- >
1137
- <span v-html="renderValidationMessage(field)" />
1138
- </FieldError>
1139
- </FieldContent>
1140
- </Field>
1141
- </template>
1221
+ <span v-html="renderValidationMessage(field)" />
1222
+ </FieldError>
1223
+ </FieldContent>
1224
+ </Field>
1225
+ </template>
1226
+ </FieldSet>
1142
1227
 
1143
1228
  <slot />
1144
1229
  </div>