@shwfed/nuxt 0.11.36 → 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.
Files changed (22) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/fields.d.vue.ts +396 -342
  3. package/dist/runtime/components/fields.vue +2 -1
  4. package/dist/runtime/components/fields.vue.d.ts +396 -342
  5. package/dist/runtime/components/modal.vue +2 -2
  6. package/dist/runtime/components/ui/button-configurator/ButtonConfiguratorDialog.vue +3 -3
  7. package/dist/runtime/components/ui/command/CommandDialog.vue +3 -3
  8. package/dist/runtime/components/ui/dialog/DialogScrollContent.d.vue.ts +8 -3
  9. package/dist/runtime/components/ui/dialog/DialogScrollContent.vue +167 -14
  10. package/dist/runtime/components/ui/dialog/DialogScrollContent.vue.d.ts +8 -3
  11. package/dist/runtime/components/ui/fields/Fields.d.vue.ts +846 -350
  12. package/dist/runtime/components/ui/fields/Fields.vue +538 -435
  13. package/dist/runtime/components/ui/fields/Fields.vue.d.ts +846 -350
  14. package/dist/runtime/components/ui/fields/schema.d.ts +3337 -30
  15. package/dist/runtime/components/ui/fields/schema.js +86 -9
  16. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.d.vue.ts +394 -340
  17. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +767 -175
  18. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue.d.ts +394 -340
  19. package/dist/runtime/components/ui/menu-tabs-configurator/MenuTabsConfiguratorDialog.vue +3 -3
  20. package/dist/runtime/components/ui/table/Table.vue +1 -0
  21. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue +3 -3
  22. package/package.json +1 -1
@@ -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 });
@@ -226,6 +259,24 @@ function getFileIcon(filename) {
226
259
  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
227
260
  return FILE_EXTENSION_ICONS[ext] ?? "vscode-icons:default-file";
228
261
  }
262
+ const MIME_TO_ICON = {
263
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "vscode-icons:file-type-excel",
264
+ "application/vnd.ms-excel": "vscode-icons:file-type-excel",
265
+ "application/pdf": "vscode-icons:file-type-pdf2",
266
+ "application/msword": "vscode-icons:file-type-word",
267
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "vscode-icons:file-type-word",
268
+ "application/vnd.ms-powerpoint": "vscode-icons:file-type-powerpoint",
269
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "vscode-icons:file-type-powerpoint",
270
+ "application/x-zip-compressed": "vscode-icons:file-type-zip",
271
+ "application/zip": "vscode-icons:file-type-zip",
272
+ "image/png": "vscode-icons:file-type-image",
273
+ "image/jpg": "vscode-icons:file-type-image",
274
+ "image/jpeg": "vscode-icons:file-type-image"
275
+ };
276
+ function getUploadTemplateIcon(field) {
277
+ const first = field.accept?.[0];
278
+ return (first && MIME_TO_ICON[first]) ?? "fluent:arrow-download-20-regular";
279
+ }
229
280
  function getUploadAcceptString(field) {
230
281
  if (!field.accept || field.accept.length === 0) {
231
282
  return void 0;
@@ -474,8 +525,8 @@ function validateField(field) {
474
525
  }
475
526
  function validateFields() {
476
527
  const nextValidationErrors = {};
477
- for (const field of displayConfig.value.fields) {
478
- if (isPassiveField(field)) {
528
+ for (const field of getConfigFields(displayConfig.value)) {
529
+ if (isPassiveField(field) || isLabeledDisplayField(field)) {
479
530
  continue;
480
531
  }
481
532
  const failure = getValidationFailure(field);
@@ -492,6 +543,9 @@ function isFieldInvalid(field) {
492
543
  function getFieldLabel(field) {
493
544
  return getLocalizedText(field.title, locale.value) ?? field.path;
494
545
  }
546
+ function getDisplayFieldLabel(field) {
547
+ return getLocalizedText(field.title, locale.value) ?? field.id;
548
+ }
495
549
  function isFieldRequired(field) {
496
550
  return field.required === true;
497
551
  }
@@ -500,6 +554,13 @@ function renderValidationMessage(field) {
500
554
  if (!error) return "";
501
555
  return $md.inline`${error.message}`(mergeDslContexts(error.context, dslContext));
502
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
+ }
503
564
  function isCalendarDateDisabled(field, date) {
504
565
  if (!field.disableDate) {
505
566
  return false;
@@ -556,8 +617,8 @@ watch(config, (value) => {
556
617
  }, { immediate: true });
557
618
  watchEffect(() => {
558
619
  const activePaths = /* @__PURE__ */ new Set();
559
- for (const field of displayConfig.value.fields) {
560
- 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)) {
561
622
  activePaths.add(field.path);
562
623
  }
563
624
  }
@@ -585,11 +646,13 @@ export {
585
646
  CURRENT_COMPATIBILITY_DATE,
586
647
  EmptyFieldC,
587
648
  FieldC,
649
+ FieldGroupC,
588
650
  FieldsBodyC,
589
651
  FieldsBodyInputC,
590
652
  FieldsConfigC,
591
653
  FieldsConfigInputC,
592
654
  KIND,
655
+ MarkdownFieldC,
593
656
  NumberFieldC,
594
657
  SelectFieldC,
595
658
  SlotFieldC,
@@ -607,7 +670,7 @@ export {
607
670
  :class="[
608
671
  'relative p-1 -m-1 border border-dashed',
609
672
  isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent',
610
- isConfigBordered(displayConfig) ? '!p-0 !m-0 border-solid border-zinc-200' : ''
673
+ isConfigBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
611
674
  ]"
612
675
  :style="getConfigStyle(displayConfig)"
613
676
  >
@@ -635,176 +698,455 @@ export {
635
698
  class="absolute inset-0 z-10 w-full h-full"
636
699
  />
637
700
 
638
- <template
639
- v-for="field in displayConfig.fields"
640
- :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)"
641
710
  >
642
- <div
643
- v-if="field.type === 'slot' && isConfigBordered(displayConfig)"
644
- :class="'border-b border-r border-zinc-200'"
645
- :style="getFieldStyle(field)"
711
+ <FieldLegend
712
+ v-if="getGroupTitle(group)"
713
+ data-slot="fields-group-legend"
714
+ class="px-2 pt-2"
646
715
  >
716
+ {{ getGroupTitle(group) }}
717
+ </FieldLegend>
718
+
719
+ <template
720
+ v-for="field in group.fields"
721
+ :key="field.id"
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>
647
735
  <slot
736
+ v-else-if="field.type === 'slot'"
648
737
  :name="field.id"
649
738
  :form="slotForm"
650
- :style="{}"
739
+ :style="getFieldStyle(field)"
651
740
  :valid="valid"
652
741
  />
653
- </div>
654
- <slot
655
- v-else-if="field.type === 'slot'"
656
- :name="field.id"
657
- :form="slotForm"
658
- :style="getFieldStyle(field)"
659
- :valid="valid"
660
- />
661
- <div
662
- v-else-if="field.type === 'empty'"
663
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
664
- :style="getFieldStyle(field)"
665
- />
666
- <div
667
- v-else-if="field.type === 'upload' && !isFieldHidden(field)"
668
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
669
- :style="getFieldContainerStyle(field, displayConfig)"
670
- class="flex flex-col gap-2 p-2"
671
- >
672
- <p class="text-sm font-medium text-zinc-700">
673
- <span class="inline-flex items-start gap-0.5">
674
- <span>{{ getFieldLabel(field) }}</span>
675
- <sup
676
- v-if="isFieldRequired(field)"
677
- class="text-red-500 leading-none"
678
- >*</sup>
679
- </span>
680
- </p>
681
- <button
682
- v-if="field.template"
683
- type="button"
684
- class="inline-flex items-center gap-1 self-start text-sm text-[--el-color-primary] hover:underline disabled:opacity-50"
685
- :disabled="templateDownloading[field.id]"
686
- @click="handleTemplateDownload(field)"
687
- >
688
- <Icon
689
- :icon="templateDownloading[field.id] ? 'svg-spinners:ring-resize' : 'fluent:arrow-download-20-regular'"
690
- class="text-base"
691
- />
692
- {{ t("upload-download-template") }}
693
- </button>
694
- <label
695
- v-if="getUploadFiles(field).length < getUploadMaxCount(field)"
696
- :class="[
697
- 'flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-8 transition-colors',
698
- isFieldDisabled(field) ? 'cursor-not-allowed border-zinc-200 opacity-50' : 'border-zinc-300 hover:border-zinc-400 hover:bg-zinc-50'
699
- ]"
700
- @drop="handleUploadDrop(field, $event)"
701
- @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)"
702
751
  >
703
- <Icon
704
- :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
705
- class="text-4xl text-zinc-400"
706
- />
707
- <p
708
- v-if="field.description"
709
- class="text-sm text-zinc-600"
710
- v-html="$md.inline`${getLocalizedText(field.description, locale) ?? ''}`()"
711
- />
712
- <p
713
- v-else
714
- class="text-sm text-zinc-600"
752
+ <FieldLabel
753
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
754
+ :style="getFieldLabelStyle(field, displayConfig)"
715
755
  >
716
- {{ t("upload-click-or-drag") }}
717
- </p>
718
- <p class="text-xs text-zinc-400">
719
- {{ getUploadDescription(field) || t("upload-accept-all") }}
720
- </p>
721
- <input
722
- type="file"
723
- class="sr-only"
724
- :accept="getUploadAcceptString(field)"
725
- :disabled="isFieldDisabled(field)"
726
- :multiple="getUploadMaxCount(field) !== 1"
727
- @change="handleUploadInputChange(field, $event)"
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)"
728
761
  >
729
- </label>
730
- <ul
731
- v-if="getUploadFiles(field).length > 0"
732
- 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"
733
773
  >
734
- <li
735
- v-for="(file, fileIndex) in getUploadFiles(field)"
736
- :key="`${field.id}:${fileIndex}:${file.name}`"
737
- 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"
738
791
  >
739
792
  <Icon
740
- :icon="getFileIcon(file.name)"
741
- class="shrink-0 text-lg"
793
+ :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
794
+ class="text-4xl text-zinc-400"
742
795
  />
743
- <span class="flex-1 truncate text-sm text-zinc-700">{{ file.name }}</span>
744
796
  <button
797
+ v-if="field.template"
745
798
  type="button"
746
- class="shrink-0 text-zinc-300 transition-colors hover:text-red-500"
747
- :disabled="isFieldDisabled(field)"
748
- @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)"
749
802
  >
750
803
  <Icon
751
- icon="fluent:delete-20-regular"
752
- class="text-lg"
804
+ :icon="templateDownloading[field.id] ? 'svg-spinners:ring-resize' : getUploadTemplateIcon(field)"
805
+ class="text-base"
753
806
  />
807
+ {{ getLocalizedText(field.templateName, locale) ?? t("upload-download-template") }}
754
808
  </button>
755
- </li>
756
- </ul>
757
- </div>
758
- <Field
759
- v-else-if="!isFieldHidden(field)"
760
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
761
- :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
762
- :orientation="getConfigOrientation(displayConfig)"
763
- :style="getFieldContainerStyle(field, displayConfig)"
764
- >
765
- <FieldLabel
766
- :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0"
767
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
768
- :style="getFieldLabelStyle(field, displayConfig)"
769
- >
770
- <span class="inline-flex items-start gap-0.5">
771
- <span>{{ getFieldLabel(field) }}</span>
772
- <sup
773
- v-if="isFieldRequired(field)"
774
- class="text-red-500 leading-none"
775
- >*</sup>
776
- </span>
777
- <span v-if="isCheating">
778
- <span class="font-mono">{{ field.path }}</span>
779
- </span>
780
- </FieldLabel>
781
- <FieldContent
782
- :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
783
- :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)"
784
866
  >
785
- <Popover
786
- v-if="field.type === 'calendar'"
787
- @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)"
788
886
  >
789
- <PopoverAnchor as-child>
790
- <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
791
- <PopoverTrigger as-child>
792
- <InputGroupInput
793
- :model-value="displayCalendarValue(getProperty(modelValue, field.path), field.display, field.value)"
794
- 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"
795
1091
  :disabled="isFieldDisabled(field)"
796
1092
  :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
797
- readonly
798
- @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)"
799
1101
  />
800
- </PopoverTrigger>
801
- <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">
802
1144
  <Icon
803
1145
  :icon="field.icon"
804
1146
  />
805
1147
  </InputGroupAddon>
806
1148
  <InputGroupAddon
807
- v-if="hasProperty(modelValue, field.path)"
1149
+ v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
808
1150
  align="inline-end"
809
1151
  :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
810
1152
  >
@@ -828,299 +1170,60 @@ export {
828
1170
  </TooltipContent>
829
1171
  </Tooltip>
830
1172
  </InputGroupAddon>
831
- </InputGroup>
832
- </PopoverAnchor>
833
- <PopoverContent class="w-72">
834
- <Calendar
835
- :locale="locale"
836
- :layout="field.mode"
837
- :model-value="toCalendarDateValue(getProperty(modelValue, field.path), field.value)"
838
- :disabled="isFieldDisabled(field)"
839
- :is-date-disabled="field.disableDate ? (date) => isCalendarDateDisabled(field, date) : void 0"
840
- @update:model-value="(value) => {
841
- if (value === void 0) {
842
- deleteProperty(modelValue, field.path);
843
- } else {
844
- setProperty(modelValue, field.path, format(value.toDate(getLocalTimeZone()), field.value));
845
- }
846
- }"
847
- />
848
- </PopoverContent>
849
- </Popover>
850
- <template v-else>
851
- <template v-if="field.type === 'select'">
852
- <Popover
853
- v-for="selectState in [getSelectFieldState(field)]"
854
- :key="`${field.id}:select:${selectState.selectedKey ?? 'empty'}`"
855
- :open="selectOpen[field.path] === true"
856
- @update:open="(open) => handleSelectOpenChange(field, open)"
857
- >
858
- <PopoverAnchor as-child>
859
- <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
860
- <PopoverTrigger as-child>
861
- <InputGroupInput
862
- :id="`${id}:${field.path}`"
863
- :model-value="getSelectDisplayValue(selectState, selectState.selectedKey)"
864
- :disabled="isFieldDisabled(field)"
865
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
866
- :placeholder="t('select-placeholder')"
867
- class="text-left"
868
- readonly
869
- @blur="handleSelectBlur(field)"
870
- />
871
- </PopoverTrigger>
872
- <InputGroupAddon v-if="field.icon">
873
- <Icon
874
- :icon="field.icon"
875
- />
876
- </InputGroupAddon>
877
- <InputGroupAddon
878
- v-if="hasProperty(modelValue, field.path)"
879
- align="inline-end"
880
- :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
881
- >
882
- <Tooltip :delay-duration="800">
883
- <TooltipTrigger>
884
- <InputGroupButton as-child>
885
- <button
886
- type="button"
887
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
888
- :disabled="isFieldDisabled(field)"
889
- @click="clearSelectField(field)"
890
- >
891
- <Icon
892
- icon="fluent:dismiss-20-regular"
893
- />
894
- </button>
895
- </InputGroupButton>
896
- </TooltipTrigger>
897
- <TooltipContent>
898
- {{ t("clear") }}
899
- </TooltipContent>
900
- </Tooltip>
901
- </InputGroupAddon>
902
- </InputGroup>
903
- </PopoverAnchor>
904
-
905
- <PopoverContent
906
- class="p-0"
907
- :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"
908
1176
  >
909
- <Command
910
- :model-value="selectState.selectedKey"
911
- :disabled="isFieldDisabled(field)"
912
- selection-behavior="toggle"
913
- @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"
914
1188
  >
915
- <CommandInput :placeholder="t('select-search-placeholder')" />
916
- <CommandList>
917
- <CommandEmpty as-child>
918
- <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
+ >
919
1197
  <Icon
920
- icon="fluent:app-recent-20-regular"
921
- class="text-zinc-400 text-2xl!"
1198
+ icon="fluent:dismiss-20-regular"
922
1199
  />
923
- <p class="text-zinc-500">
924
- {{ t("select-empty") }}
925
- </p>
926
- </section>
927
- </CommandEmpty>
928
- <CommandGroup>
929
- <CommandItem
930
- v-for="option in selectState.options"
931
- :key="option.key"
932
- data-slot="select-option"
933
- :value="option.key"
934
- 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"
935
- >
936
- {{ option.label }}
937
- </CommandItem>
938
- </CommandGroup>
939
- </CommandList>
940
- </Command>
941
- </PopoverContent>
942
- </Popover>
943
- </template>
944
-
945
- <template v-else-if="field.type === 'radio'">
946
- <RadioGroupRoot
947
- v-for="radioState in [getSelectFieldState(field)]"
948
- :key="`${field.id}:radio:${radioState.selectedKey ?? 'empty'}`"
949
- :model-value="radioState.selectedKey"
950
- :disabled="isFieldDisabled(field)"
951
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
952
- class="flex flex-wrap gap-x-4 gap-y-1.5"
953
- @update:model-value="(value) => handleSelectValueChange(field, radioState, value)"
954
- @focusout="validateField(field)"
955
- >
956
- <label
957
- v-for="option in radioState.options"
958
- :key="option.key"
959
- class="flex items-center gap-1.5 text-sm cursor-pointer data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
960
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
961
- >
962
- <RadioGroupItem
963
- :value="option.key"
964
- data-slot="radio-group-item"
965
- 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"
966
1210
  >
967
- <RadioGroupIndicator class="flex items-center justify-center">
968
- <span class="size-1.5 rounded-full bg-white" />
969
- </RadioGroupIndicator>
970
- </RadioGroupItem>
971
- {{ option.label }}
972
- </label>
973
- </RadioGroupRoot>
1211
+ <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1212
+ </span>
1213
+ </InputGroupAddon>
1214
+ </InputGroup>
974
1215
  </template>
975
1216
 
976
- <InputGroup
977
- v-else
978
- :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
979
- :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"
980
1220
  >
981
- <div
982
- v-if="field.type === 'textarea'"
983
- class="flex min-w-0 w-full items-center"
984
- >
985
- <InputGroupTextarea
986
- :id="`${id}:${field.path}`"
987
- :model-value="getProperty(modelValue, field.path)"
988
- :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
989
- :disabled="isFieldDisabled(field)"
990
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
991
- @update:model-value="(value) => {
992
- if (!value && !field.discardEmptyString) {
993
- deleteProperty(modelValue, field.path);
994
- } else {
995
- setProperty(modelValue, field.path, value);
996
- }
997
- }"
998
- @blur="validateField(field)"
999
- />
1000
- <InputGroupAddon v-if="field.icon">
1001
- <Icon
1002
- :icon="field.icon"
1003
- />
1004
- </InputGroupAddon>
1005
- </div>
1006
- <InputGroupInput
1007
- v-if="field.type === 'string'"
1008
- :id="`${id}:${field.path}`"
1009
- :treat-empty-as-different-state-from-null="!!field.discardEmptyString"
1010
- :model-value="getProperty(modelValue, field.path)"
1011
- :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1012
- :disabled="isFieldDisabled(field)"
1013
- :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1014
- @update:model-value="(value) => {
1015
- if (!value && !field.discardEmptyString) {
1016
- deleteProperty(modelValue, field.path);
1017
- } else {
1018
- setProperty(modelValue, field.path, value);
1019
- }
1020
- }"
1021
- @blur="validateField(field)"
1022
- />
1023
- <InputGroupNumberField
1024
- v-if="field.type === 'number'"
1025
- :id="`${id}:${field.path}`"
1026
- :model-value="getProperty(modelValue, field.path) ?? null"
1027
- :min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
1028
- :max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
1029
- :step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
1030
- :disabled="isFieldDisabled(field)"
1031
- :invalid="isFieldInvalid(field)"
1032
- @update:model-value="(value) => {
1033
- if (!value && value !== 0) {
1034
- deleteProperty(modelValue, field.path);
1035
- } else {
1036
- setProperty(modelValue, field.path, value);
1037
- }
1038
- }"
1039
- @blur="validateField(field)"
1040
- />
1041
- <InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
1042
- <Icon
1043
- :icon="field.icon"
1044
- />
1045
- </InputGroupAddon>
1046
- <InputGroupAddon
1047
- v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
1048
- align="inline-end"
1049
- :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
1050
- >
1051
- <Tooltip :delay-duration="800">
1052
- <TooltipTrigger>
1053
- <InputGroupButton as-child>
1054
- <button
1055
- type="button"
1056
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
1057
- :disabled="isFieldDisabled(field)"
1058
- @click="deleteProperty(modelValue, field.path)"
1059
- >
1060
- <Icon
1061
- icon="fluent:dismiss-20-regular"
1062
- />
1063
- </button>
1064
- </InputGroupButton>
1065
- </TooltipTrigger>
1066
- <TooltipContent>
1067
- {{ t("clear") }}
1068
- </TooltipContent>
1069
- </Tooltip>
1070
- </InputGroupAddon>
1071
- <InputGroupAddon
1072
- v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
1073
- align="inline-end"
1074
- >
1075
- <span class="text-xs text-zinc-400 font-mono">
1076
- <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1077
- </span>
1078
- </InputGroupAddon>
1079
- <InputGroupAddon
1080
- v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
1081
- align="block-end"
1082
- >
1083
- <Tooltip
1084
- v-if="hasProperty(modelValue, field.path)"
1085
- :delay-duration="800"
1086
- >
1087
- <TooltipTrigger>
1088
- <InputGroupButton as-child>
1089
- <button
1090
- type="button"
1091
- class="text-zinc-300 hover:text-zinc-500 transition-colors"
1092
- :disabled="isFieldDisabled(field)"
1093
- @click="deleteProperty(modelValue, field.path)"
1094
- >
1095
- <Icon
1096
- icon="fluent:dismiss-20-regular"
1097
- />
1098
- </button>
1099
- </InputGroupButton>
1100
- </TooltipTrigger>
1101
- <TooltipContent>
1102
- {{ t("clear") }}
1103
- </TooltipContent>
1104
- </Tooltip>
1105
- <span
1106
- v-if="field.maxLength && getProperty(modelValue, field.path)"
1107
- class="text-xs text-zinc-400 font-mono"
1108
- >
1109
- <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1110
- </span>
1111
- </InputGroupAddon>
1112
- </InputGroup>
1113
- </template>
1114
-
1115
- <FieldError
1116
- v-if="isFieldInvalid(field)"
1117
- :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
1118
- >
1119
- <span v-html="renderValidationMessage(field)" />
1120
- </FieldError>
1121
- </FieldContent>
1122
- </Field>
1123
- </template>
1221
+ <span v-html="renderValidationMessage(field)" />
1222
+ </FieldError>
1223
+ </FieldContent>
1224
+ </Field>
1225
+ </template>
1226
+ </FieldSet>
1124
1227
 
1125
1228
  <slot />
1126
1229
  </div>