@shwfed/nuxt 0.11.49 → 0.11.51

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 (30) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/button.d.vue.ts +8 -0
  3. package/dist/runtime/components/button.vue.d.ts +8 -0
  4. package/dist/runtime/components/fields.d.vue.ts +850 -6
  5. package/dist/runtime/components/fields.vue +0 -2
  6. package/dist/runtime/components/fields.vue.d.ts +850 -6
  7. package/dist/runtime/components/ui/button-configurator/ButtonConfiguratorDialog.d.vue.ts +8 -0
  8. package/dist/runtime/components/ui/button-configurator/ButtonConfiguratorDialog.vue +137 -0
  9. package/dist/runtime/components/ui/button-configurator/ButtonConfiguratorDialog.vue.d.ts +8 -0
  10. package/dist/runtime/components/ui/button-configurator/menu.d.ts +2 -0
  11. package/dist/runtime/components/ui/button-configurator/menu.js +14 -0
  12. package/dist/runtime/components/ui/buttons/Buttons.d.vue.ts +8 -0
  13. package/dist/runtime/components/ui/buttons/Buttons.vue +42 -14
  14. package/dist/runtime/components/ui/buttons/Buttons.vue.d.ts +8 -0
  15. package/dist/runtime/components/ui/buttons/schema.d.ts +30 -0
  16. package/dist/runtime/components/ui/buttons/schema.js +5 -0
  17. package/dist/runtime/components/ui/fields/Fields.d.vue.ts +1698 -10
  18. package/dist/runtime/components/ui/fields/Fields.vue +627 -162
  19. package/dist/runtime/components/ui/fields/Fields.vue.d.ts +1698 -10
  20. package/dist/runtime/components/ui/fields/schema.d.ts +5625 -153
  21. package/dist/runtime/components/ui/fields/schema.js +83 -80
  22. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.d.vue.ts +849 -5
  23. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +224 -618
  24. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue.d.ts +849 -5
  25. package/package.json +1 -1
  26. package/dist/runtime/components/ui/fields/FieldsBody.d.vue.ts +0 -17
  27. package/dist/runtime/components/ui/fields/FieldsBody.vue +0 -720
  28. package/dist/runtime/components/ui/fields/FieldsBody.vue.d.ts +0 -17
  29. package/dist/runtime/components/ui/fields/render-context.d.ts +0 -120
  30. package/dist/runtime/components/ui/fields/render-context.js +0 -0
@@ -6,15 +6,21 @@ import { CalendarDate, getLocalTimeZone } from "@internationalized/date";
6
6
  import { Icon } from "@iconify/vue";
7
7
  import { Effect } from "effect";
8
8
  import { format, parse } from "date-fns";
9
- import { deleteProperty, getProperty, setProperty } from "dot-prop";
9
+ import { deleteProperty, getProperty, hasProperty, setProperty } from "dot-prop";
10
10
  import { computed, nextTick, readonly, ref, toRaw, useId, watch, watchEffect } from "vue";
11
11
  import { useI18n } from "vue-i18n";
12
12
  import { useCheating } from "#imports";
13
+ import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "reka-ui";
13
14
  import { mergeDslContexts, useCELContext } from "../../../plugins/cel/context";
15
+ import { Calendar } from "../calendar";
14
16
  import { Button } from "../button";
15
- import FieldsBody from "./FieldsBody.vue";
17
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
18
+ import { Field, FieldContent, FieldError, FieldLabel, FieldSet } from "../field";
16
19
  import FieldsConfiguratorDialog from "../fields-configurator/FieldsConfiguratorDialog.vue";
20
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupNumberField, InputGroupTextarea } from "../input-group";
21
+ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../popover";
17
22
  import { Skeleton } from "../skeleton";
23
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip";
18
24
  import { getLocalizedText } from "../../../utils/coders";
19
25
  import { FieldsConfigC, createFieldsConfig } from "./schema";
20
26
  const id = useId();
@@ -25,7 +31,7 @@ const defaultConfig = createFieldsConfig({
25
31
  const props = defineProps({
26
32
  config: { type: null, required: true }
27
33
  });
28
- const slots = defineSlots();
34
+ defineSlots();
29
35
  const emit = defineEmits(["update:config", "initial-value-ready"]);
30
36
  const config = computedAsync(async () => FieldsConfigC.parse(await props.config.pipe(Effect.runPromise) ?? defaultConfig));
31
37
  const { t, locale } = useI18n();
@@ -44,23 +50,35 @@ const isReady = ref(false);
44
50
  const hasInitializedFieldValues = ref(false);
45
51
  const hasEmittedInitialValueReady = ref(false);
46
52
  const readyResolvers = [];
47
- function requireSlotProps(slotProps) {
48
- if (!slotProps) {
49
- throw new TypeError("missing slot props");
50
- }
51
- return slotProps;
52
- }
53
53
  function cloneConfig(config2) {
54
- return structuredClone(toRaw(config2));
54
+ const nextConfig = {
55
+ kind: config2.kind,
56
+ compatibilityDate: config2.compatibilityDate,
57
+ fields: config2.fields.slice(),
58
+ groups: config2.groups.map((group) => ({
59
+ ...group,
60
+ fields: group.fields.slice()
61
+ }))
62
+ };
63
+ if (config2.orientation) {
64
+ nextConfig.orientation = config2.orientation;
65
+ }
66
+ if (config2.bordered) {
67
+ nextConfig.bordered = config2.bordered;
68
+ }
69
+ if (config2.style) {
70
+ nextConfig.style = config2.style;
71
+ }
72
+ return nextConfig;
55
73
  }
56
- function getBodyOrientation(body) {
57
- return body.orientation ?? "horizontal";
74
+ function getConfigOrientation(config2) {
75
+ return config2.orientation ?? "horizontal";
58
76
  }
59
- function usesContentsOrientation(body) {
60
- return getBodyOrientation(body) === "contents";
77
+ function usesContentsOrientation(config2) {
78
+ return getConfigOrientation(config2) === "contents";
61
79
  }
62
- function isBodyBordered(body) {
63
- return usesContentsOrientation(body) && body.bordered === true;
80
+ function isConfigBordered(config2) {
81
+ return usesContentsOrientation(config2) && config2.bordered === true;
64
82
  }
65
83
  function tryEvaluateExpression(source, context) {
66
84
  try {
@@ -81,12 +99,12 @@ function evaluateExpression(source, context, fallback) {
81
99
  }
82
100
  return result.value;
83
101
  }
84
- function getBodyStyle(body) {
102
+ function getConfigStyle(config2) {
85
103
  const style = {};
86
- if (!body.style) {
104
+ if (!config2.style) {
87
105
  return style;
88
106
  }
89
- const styleMap = evaluateExpression(body.style, { form: modelValue.value }, {});
107
+ const styleMap = evaluateExpression(config2.style, { form: modelValue.value }, {});
90
108
  if (!styleMap || typeof styleMap !== "object" || Array.isArray(styleMap)) {
91
109
  return style;
92
110
  }
@@ -116,8 +134,8 @@ function getFieldPartStyle(styleExpression) {
116
134
  function getFieldStyle(field) {
117
135
  return getFieldPartStyle(field.style);
118
136
  }
119
- function getGroupStyle(group, body) {
120
- const style = usesContentsOrientation(body) ? getBodyStyle(body) : {};
137
+ function getGroupStyle(group, config2) {
138
+ const style = usesContentsOrientation(config2) ? getConfigStyle(config2) : {};
121
139
  const groupStyle = group.style ? evaluateExpression(group.style, { form: modelValue.value, id: group.id }, {}) : {};
122
140
  if (!groupStyle || typeof groupStyle !== "object" || Array.isArray(groupStyle)) {
123
141
  return style;
@@ -129,57 +147,42 @@ function getGroupStyle(group, body) {
129
147
  }
130
148
  return style;
131
149
  }
132
- function getBodyEntries(body) {
150
+ function getConfigEntries(config2) {
133
151
  return [
134
- ...body.fields.map((field) => ({
152
+ ...config2.fields.map((field) => ({
135
153
  key: `field:${field.id}`,
136
154
  field
137
155
  })),
138
- ...(body.groups ?? []).map((group) => ({
156
+ ...config2.groups.map((group) => ({
139
157
  key: `group:${group.id}`,
140
158
  group
141
159
  }))
142
160
  ];
143
161
  }
144
- function visitFields(fields, visitor, visible) {
145
- for (const field of fields) {
146
- const fieldVisible = field.type === "empty" ? visible : visible && !isFieldHidden(field);
147
- visitor(field, fieldVisible);
148
- if (field.type === "container") {
149
- visitFields(field.fields, visitor, fieldVisible);
150
- }
151
- }
152
- }
153
- function visitBodyFields(body, visitor) {
154
- visitFields(body.fields, visitor, true);
155
- for (const group of body.groups ?? []) {
156
- visitFields(group.fields, visitor, true);
157
- }
162
+ function getConfigFields(config2) {
163
+ return [...config2.fields, ...config2.groups.flatMap((group) => group.fields)];
158
164
  }
159
- function getFieldContainerStyle(field, body) {
160
- if (field.type === "slot" || field.type === "container") {
161
- return {};
162
- }
163
- if (!isPassiveField(field) && usesContentsOrientation(body)) {
165
+ function getFieldContainerStyle(field, config2) {
166
+ if (!isPassiveField(field) && usesContentsOrientation(config2)) {
164
167
  return {};
165
168
  }
166
169
  return getFieldStyle(field);
167
170
  }
168
- function getFieldLabelStyle(field, body) {
169
- if (!usesContentsOrientation(body)) {
171
+ function getFieldLabelStyle(field, config2) {
172
+ if (!usesContentsOrientation(config2)) {
170
173
  return {};
171
174
  }
172
175
  return getFieldPartStyle(field.labelStyle);
173
176
  }
174
- function getFieldContentStyle(field, body) {
175
- const style = usesContentsOrientation(body) ? getFieldPartStyle(field.contentStyle) : {};
176
- if (usesContentsOrientation(body) && isFieldLabelHidden(field) && style.gridColumn === void 0) {
177
+ function getFieldContentStyle(field, config2) {
178
+ const style = usesContentsOrientation(config2) ? getFieldPartStyle(field.contentStyle) : {};
179
+ if (usesContentsOrientation(config2) && isFieldLabelHidden(field) && style.gridColumn === void 0) {
177
180
  style.gridColumn = "1 / -1";
178
181
  }
179
182
  return style;
180
183
  }
181
- function getMarkdownBodyContentStyle(field, body) {
182
- if (!usesContentsOrientation(body)) {
184
+ function getMarkdownBodyContentStyle(field, config2) {
185
+ if (!usesContentsOrientation(config2)) {
183
186
  return {};
184
187
  }
185
188
  return {
@@ -190,8 +193,11 @@ function getMarkdownBodyContentStyle(field, body) {
190
193
  function isPassiveField(field) {
191
194
  return field.type === "slot" || field.type === "empty";
192
195
  }
196
+ function isMarkdownDisplayField(field) {
197
+ return field.type === "markdown" || field.type === "markdown-body";
198
+ }
193
199
  function isInteractiveField(field) {
194
- return field.type !== "slot" && field.type !== "empty" && field.type !== "markdown" && field.type !== "markdown-body" && field.type !== "container";
200
+ return !isMarkdownDisplayField(field);
195
201
  }
196
202
  function isFieldLabelHidden(field) {
197
203
  return field.hideLabel === true;
@@ -219,13 +225,13 @@ function getFieldValue(field) {
219
225
  return getProperty(modelValue.value, field.path);
220
226
  }
221
227
  function initializeFieldValues(config2) {
222
- visitBodyFields(config2, (field) => {
223
- if (!isInteractiveField(field) || !field.initialValue) {
224
- return;
228
+ for (const field of getConfigFields(config2)) {
229
+ if (isPassiveField(field) || isMarkdownDisplayField(field) || !field.initialValue) {
230
+ continue;
225
231
  }
226
232
  const initialValueResult = tryEvaluateExpression(field.initialValue, { form: modelValue.value });
227
233
  if (!initialValueResult.ok) {
228
- return;
234
+ continue;
229
235
  }
230
236
  const initialValue = initialValueResult.value;
231
237
  setProperty(
@@ -233,7 +239,7 @@ function initializeFieldValues(config2) {
233
239
  field.path,
234
240
  field.type === "number" && typeof initialValue === "bigint" ? Number(initialValue) : initialValue
235
241
  );
236
- });
242
+ }
237
243
  }
238
244
  const MIME_LABELS = {
239
245
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel",
@@ -538,15 +544,15 @@ function validateField(field) {
538
544
  }
539
545
  function validateFields() {
540
546
  const nextValidationErrors = {};
541
- visitBodyFields(displayConfig.value, (field, visible) => {
542
- if (!visible || !isInteractiveField(field)) {
543
- return;
547
+ for (const field of getConfigFields(displayConfig.value)) {
548
+ if (isPassiveField(field) || isMarkdownDisplayField(field)) {
549
+ continue;
544
550
  }
545
551
  const failure = getValidationFailure(field);
546
552
  if (failure) {
547
553
  nextValidationErrors[field.path] = failure;
548
554
  }
549
- });
555
+ }
550
556
  validationErrors.value = nextValidationErrors;
551
557
  return Object.keys(nextValidationErrors).length === 0;
552
558
  }
@@ -557,10 +563,7 @@ function getFieldLabel(field) {
557
563
  return getLocalizedText(field.title, locale.value) ?? field.path;
558
564
  }
559
565
  function getDisplayFieldLabel(field) {
560
- if ("title" in field && field.title) {
561
- return getLocalizedText(field.title, locale.value) ?? field.id;
562
- }
563
- return field.id;
566
+ return getLocalizedText(field.title, locale.value) ?? field.id;
564
567
  }
565
568
  function isFieldRequired(field) {
566
569
  return field.required === true;
@@ -612,30 +615,6 @@ function handleCalendarBlur(field) {
612
615
  }
613
616
  }, 0);
614
617
  }
615
- function getFieldMaxLength(field) {
616
- if (!field.maxLength) {
617
- return void 0;
618
- }
619
- return evaluateExpression(field.maxLength, void 0, void 0);
620
- }
621
- function getNumberFieldMin(field) {
622
- if (!field.min) {
623
- return void 0;
624
- }
625
- return evaluateExpression(field.min, void 0, void 0);
626
- }
627
- function getNumberFieldMax(field) {
628
- if (!field.max) {
629
- return void 0;
630
- }
631
- return evaluateExpression(field.max, void 0, void 0);
632
- }
633
- function getNumberFieldStep(field) {
634
- if (!field.step) {
635
- return void 0;
636
- }
637
- return evaluateExpression(field.step, void 0, void 0);
638
- }
639
618
  function handleConfiguratorConfirm(nextConfig) {
640
619
  displayConfig.value = cloneConfig(nextConfig);
641
620
  emit("update:config", nextConfig);
@@ -667,11 +646,11 @@ watch(config, (value) => {
667
646
  }, { immediate: true });
668
647
  watchEffect(() => {
669
648
  const activePaths = /* @__PURE__ */ new Set();
670
- visitBodyFields(displayConfig.value, (field, visible) => {
671
- if (visible && isInteractiveField(field) && !isFieldDisabled(field)) {
649
+ for (const field of getConfigFields(displayConfig.value)) {
650
+ if (!isPassiveField(field) && !isFieldHidden(field) && !isMarkdownDisplayField(field) && !isFieldDisabled(field)) {
672
651
  activePaths.add(field.path);
673
652
  }
674
- });
653
+ }
675
654
  for (const path of Object.keys(validationErrors.value)) {
676
655
  if (!activePaths.has(path)) {
677
656
  clearFieldValidation(path);
@@ -688,71 +667,11 @@ watchEffect(() => {
688
667
  }
689
668
  }
690
669
  });
691
- const renderContext = computed(() => ({
692
- id,
693
- isCheating: isCheating.value,
694
- modelValue: modelValue.value,
695
- slotForm: slotForm.value,
696
- valid,
697
- calendarOpen: calendarOpen.value,
698
- selectOpen: selectOpen.value,
699
- templateDownloading: templateDownloading.value,
700
- getBodyEntries,
701
- getBodyOrientation,
702
- isBodyBordered,
703
- getBodyStyle,
704
- getGroupStyle,
705
- getFieldStyle,
706
- getFieldContainerStyle,
707
- getFieldLabelStyle,
708
- getFieldContentStyle,
709
- getMarkdownBodyContentStyle,
710
- isInteractiveField,
711
- isFieldLabelHidden,
712
- isFieldHidden,
713
- isFieldDisabled,
714
- isFieldInvalid,
715
- getFieldLabel,
716
- getDisplayFieldLabel,
717
- isFieldRequired,
718
- renderValidationMessage,
719
- renderMarkdownField,
720
- renderMarkdownBodyField,
721
- toCalendarDateValue,
722
- displayCalendarValue,
723
- isCalendarDateDisabled,
724
- handleCalendarOpenChange,
725
- handleCalendarBlur,
726
- validateField,
727
- getFieldMaxLength,
728
- getNumberFieldMin,
729
- getNumberFieldMax,
730
- getNumberFieldStep,
731
- getSelectFieldState,
732
- getSelectDisplayValue,
733
- handleSelectValueChange,
734
- handleSelectOpenChange,
735
- handleSelectBlur,
736
- handleSelectCommandValueChange,
737
- clearSelectField,
738
- getUploadTemplateIcon,
739
- getUploadAcceptString,
740
- getUploadDescription,
741
- getUploadMaxCount,
742
- getUploadFiles,
743
- handleUploadInputChange,
744
- handleUploadDrop,
745
- handleUploadDragOver,
746
- handleTemplateDownload,
747
- getFileIcon,
748
- removeUploadFile
749
- }));
750
670
  </script>
751
671
 
752
672
  <script>
753
673
  export {
754
674
  CalendarFieldC,
755
- ContainerFieldC,
756
675
  CURRENT_COMPATIBILITY_DATE,
757
676
  EmptyFieldC,
758
677
  FieldC,
@@ -769,7 +688,6 @@ export {
769
688
  SlotFieldC,
770
689
  SUPPORTED_COMPATIBILITY_DATES,
771
690
  StringFieldC,
772
- TextareaFieldC,
773
691
  UploadFieldC,
774
692
  ValidationRuleC,
775
693
  createFieldsConfig,
@@ -782,9 +700,9 @@ export {
782
700
  :class="[
783
701
  'relative p-1 -m-1 border border-dashed',
784
702
  isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent',
785
- isBodyBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
703
+ isConfigBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
786
704
  ]"
787
- :style="getBodyStyle(displayConfig)"
705
+ :style="getConfigStyle(displayConfig)"
788
706
  >
789
707
  <Button
790
708
  v-if="isCheating"
@@ -810,20 +728,567 @@ export {
810
728
  class="absolute inset-0 z-10 w-full h-full"
811
729
  />
812
730
 
813
- <FieldsBody
814
- :body="displayConfig"
815
- :renderer="renderContext"
731
+ <component
732
+ :is="entry.group ? FieldSet : 'div'"
733
+ v-for="entry in getConfigEntries(displayConfig)"
734
+ :key="entry.key"
735
+ :data-slot="entry.group ? 'fields-group' : 'fields-root-entry'"
736
+ :data-group-id="entry.group?.id"
737
+ :class="entry.group ? [
738
+ isConfigBordered(displayConfig) ? 'overflow-hidden border border-zinc-200 [[data-slot=fields-group]+&]:border-t-0 [&>[data-slot=field]:last-child>[data-slot=field-label]]:border-b-0 [&>[data-slot=field]:last-child>[data-slot=field-content]]:border-r-0 [&>[data-slot=field]:last-child>[data-slot=field-content]]:border-b-0 [&>div:last-child]:border-r-0 [&>div:last-child]:border-b-0' : ''
739
+ ] : void 0"
740
+ :style="entry.group ? getGroupStyle(entry.group, displayConfig) : { display: 'contents' }"
816
741
  >
817
742
  <template
818
- v-for="(_, slotName) in slots"
819
- #[slotName]="slotProps"
743
+ v-for="field in entry.group ? entry.group.fields : entry.field ? [entry.field] : []"
744
+ :key="field.id"
820
745
  >
746
+ <div
747
+ v-if="field.type === 'slot' && isConfigBordered(displayConfig)"
748
+ :class="'border-b border-r border-zinc-200'"
749
+ :style="getFieldStyle(field)"
750
+ >
751
+ <slot
752
+ :name="field.id"
753
+ :form="slotForm"
754
+ :style="{}"
755
+ :valid="valid"
756
+ />
757
+ </div>
821
758
  <slot
822
- :name="slotName"
823
- v-bind="requireSlotProps(slotProps)"
759
+ v-else-if="field.type === 'slot'"
760
+ :name="field.id"
761
+ :form="slotForm"
762
+ :style="getFieldStyle(field)"
763
+ :valid="valid"
764
+ />
765
+ <div
766
+ v-else-if="field.type === 'empty'"
767
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
768
+ :style="getFieldStyle(field)"
824
769
  />
770
+ <Field
771
+ v-else-if="field.type === 'markdown' && !isFieldHidden(field)"
772
+ :orientation="getConfigOrientation(displayConfig)"
773
+ :style="getFieldContainerStyle(field, displayConfig)"
774
+ >
775
+ <FieldLabel
776
+ v-if="!isFieldLabelHidden(field)"
777
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
778
+ :style="getFieldLabelStyle(field, displayConfig)"
779
+ >
780
+ <span>{{ getDisplayFieldLabel(field) }}</span>
781
+ </FieldLabel>
782
+ <FieldContent
783
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2 items-center justify-center text-center' : void 0"
784
+ :style="getFieldContentStyle(field, displayConfig)"
785
+ >
786
+ <div
787
+ class="text-center"
788
+ data-slot="fields-markdown"
789
+ v-html="renderMarkdownField(field)"
790
+ />
791
+ </FieldContent>
792
+ </Field>
793
+ <Field
794
+ v-else-if="field.type === 'markdown-body' && !isFieldHidden(field)"
795
+ :orientation="getConfigOrientation(displayConfig)"
796
+ :style="getFieldContainerStyle(field, displayConfig)"
797
+ >
798
+ <FieldContent
799
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
800
+ :style="getMarkdownBodyContentStyle(field, displayConfig)"
801
+ >
802
+ <div
803
+ data-slot="fields-markdown-body"
804
+ v-html="renderMarkdownBodyField(field)"
805
+ />
806
+ </FieldContent>
807
+ </Field>
808
+ <Field
809
+ v-else-if="field.type === 'upload' && !isFieldHidden(field)"
810
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
811
+ :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
812
+ :orientation="getConfigOrientation(displayConfig)"
813
+ :style="getFieldContainerStyle(field, displayConfig)"
814
+ >
815
+ <FieldLabel
816
+ v-if="!isFieldLabelHidden(field)"
817
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
818
+ :style="getFieldLabelStyle(field, displayConfig)"
819
+ >
820
+ <span class="inline-flex items-start gap-0.5">
821
+ <span>{{ getFieldLabel(field) }}</span>
822
+ <sup
823
+ v-if="isFieldRequired(field)"
824
+ class="text-red-500 leading-none"
825
+ >*</sup>
826
+ </span>
827
+ <span v-if="isCheating">
828
+ <span class="font-mono">{{ field.path }}</span>
829
+ </span>
830
+ </FieldLabel>
831
+ <FieldContent
832
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
833
+ :style="getFieldContentStyle(field, displayConfig)"
834
+ >
835
+ <div
836
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
837
+ class="flex flex-col gap-2"
838
+ >
839
+ <label
840
+ v-if="getUploadFiles(field).length < getUploadMaxCount(field)"
841
+ :class="[
842
+ 'flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-4 py-8 transition-colors',
843
+ isFieldDisabled(field) ? 'cursor-not-allowed border-zinc-200 opacity-50' : 'border-zinc-300 hover:border-zinc-400 hover:bg-zinc-50'
844
+ ]"
845
+ @drop="handleUploadDrop(field, $event)"
846
+ @dragover="handleUploadDragOver"
847
+ >
848
+ <Icon
849
+ :icon="field.icon ?? 'fluent:cloud-arrow-up-20-regular'"
850
+ class="text-4xl text-zinc-400"
851
+ />
852
+ <button
853
+ v-if="field.template"
854
+ type="button"
855
+ 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"
856
+ :disabled="templateDownloading[field.id]"
857
+ @click.prevent.stop="handleTemplateDownload(field)"
858
+ >
859
+ <Icon
860
+ :icon="templateDownloading[field.id] ? 'svg-spinners:ring-resize' : getUploadTemplateIcon(field)"
861
+ class="text-base"
862
+ />
863
+ {{ getLocalizedText(field.templateName, locale) ?? t("upload-download-template") }}
864
+ </button>
865
+ <p
866
+ v-if="field.description"
867
+ class="text-sm text-zinc-600"
868
+ v-html="$md.inline`${getLocalizedText(field.description, locale) ?? ''}`()"
869
+ />
870
+ <p
871
+ v-else
872
+ class="text-sm text-zinc-600"
873
+ >
874
+ {{ t("upload-click-or-drag") }}
875
+ </p>
876
+ <p class="text-xs text-zinc-400">
877
+ {{ getUploadDescription(field) || t("upload-accept-all") }}
878
+ </p>
879
+ <input
880
+ type="file"
881
+ class="sr-only"
882
+ :accept="getUploadAcceptString(field)"
883
+ :disabled="isFieldDisabled(field)"
884
+ :multiple="getUploadMaxCount(field) !== 1"
885
+ @change="handleUploadInputChange(field, $event)"
886
+ >
887
+ </label>
888
+ <ul
889
+ v-if="getUploadFiles(field).length > 0"
890
+ class="flex flex-col gap-1"
891
+ >
892
+ <li
893
+ v-for="(file, fileIndex) in getUploadFiles(field)"
894
+ :key="`${field.id}:${fileIndex}:${file.name}`"
895
+ class="flex items-center gap-2 rounded-md border border-zinc-200 px-3 py-2"
896
+ >
897
+ <Icon
898
+ :icon="getFileIcon(file.name)"
899
+ class="shrink-0 text-lg"
900
+ />
901
+ <span class="flex-1 truncate text-sm text-zinc-700">{{ file.name }}</span>
902
+ <button
903
+ type="button"
904
+ class="shrink-0 text-zinc-300 transition-colors hover:text-red-500"
905
+ :disabled="isFieldDisabled(field)"
906
+ @click="removeUploadFile(field, fileIndex)"
907
+ >
908
+ <Icon
909
+ icon="fluent:delete-20-regular"
910
+ class="text-lg"
911
+ />
912
+ </button>
913
+ </li>
914
+ </ul>
915
+ </div>
916
+ <FieldError
917
+ v-if="isFieldInvalid(field)"
918
+ :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
919
+ >
920
+ <span v-html="renderValidationMessage(field)" />
921
+ </FieldError>
922
+ </FieldContent>
923
+ </Field>
924
+ <Field
925
+ v-else-if="isInteractiveField(field) && !isFieldHidden(field)"
926
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
927
+ :data-invalid="isFieldInvalid(field) ? 'true' : void 0"
928
+ :orientation="getConfigOrientation(displayConfig)"
929
+ :style="getFieldContainerStyle(field, displayConfig)"
930
+ >
931
+ <FieldLabel
932
+ v-if="!isFieldLabelHidden(field)"
933
+ :for="['string', 'textarea', 'number', 'select'].includes(field.type) ? `${id}:${field.path}` : void 0"
934
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200' : void 0"
935
+ :style="getFieldLabelStyle(field, displayConfig)"
936
+ >
937
+ <span class="inline-flex items-start gap-0.5">
938
+ <span>{{ getFieldLabel(field) }}</span>
939
+ <sup
940
+ v-if="isFieldRequired(field)"
941
+ class="text-red-500 leading-none"
942
+ >*</sup>
943
+ </span>
944
+ <span v-if="isCheating">
945
+ <span class="font-mono">{{ field.path }}</span>
946
+ </span>
947
+ </FieldLabel>
948
+ <FieldContent
949
+ :class="isConfigBordered(displayConfig) ? 'border-b border-r border-zinc-200 p-2' : void 0"
950
+ :style="getFieldContentStyle(field, displayConfig)"
951
+ >
952
+ <Popover
953
+ v-if="field.type === 'calendar'"
954
+ @update:open="(open) => handleCalendarOpenChange(field, open)"
955
+ >
956
+ <PopoverAnchor as-child>
957
+ <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
958
+ <PopoverTrigger as-child>
959
+ <InputGroupInput
960
+ :model-value="displayCalendarValue(getProperty(modelValue, field.path), field.display, field.value)"
961
+ class="text-left"
962
+ :disabled="isFieldDisabled(field)"
963
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
964
+ readonly
965
+ @blur="handleCalendarBlur(field)"
966
+ />
967
+ </PopoverTrigger>
968
+ <InputGroupAddon v-if="field.icon">
969
+ <Icon
970
+ :icon="field.icon"
971
+ />
972
+ </InputGroupAddon>
973
+ <InputGroupAddon
974
+ v-if="hasProperty(modelValue, field.path)"
975
+ align="inline-end"
976
+ :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
977
+ >
978
+ <Tooltip :delay-duration="800">
979
+ <TooltipTrigger>
980
+ <InputGroupButton as-child>
981
+ <button
982
+ type="button"
983
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
984
+ :disabled="isFieldDisabled(field)"
985
+ @click="deleteProperty(modelValue, field.path)"
986
+ >
987
+ <Icon
988
+ icon="fluent:dismiss-20-regular"
989
+ />
990
+ </button>
991
+ </InputGroupButton>
992
+ </TooltipTrigger>
993
+ <TooltipContent>
994
+ {{ t("clear") }}
995
+ </TooltipContent>
996
+ </Tooltip>
997
+ </InputGroupAddon>
998
+ </InputGroup>
999
+ </PopoverAnchor>
1000
+ <PopoverContent class="w-72">
1001
+ <Calendar
1002
+ :locale="locale"
1003
+ :layout="field.mode"
1004
+ :model-value="toCalendarDateValue(getProperty(modelValue, field.path), field.value)"
1005
+ :disabled="isFieldDisabled(field)"
1006
+ :is-date-disabled="field.disableDate ? (date) => isCalendarDateDisabled(field, date) : void 0"
1007
+ @update:model-value="(value) => {
1008
+ if (value === void 0) {
1009
+ deleteProperty(modelValue, field.path);
1010
+ } else {
1011
+ setProperty(modelValue, field.path, format(value.toDate(getLocalTimeZone()), field.value));
1012
+ }
1013
+ }"
1014
+ />
1015
+ </PopoverContent>
1016
+ </Popover>
1017
+ <template v-else>
1018
+ <template v-if="field.type === 'select'">
1019
+ <Popover
1020
+ v-for="selectState in [getSelectFieldState(field)]"
1021
+ :key="`${field.id}:select:${selectState.selectedKey ?? 'empty'}`"
1022
+ :open="selectOpen[field.path] === true"
1023
+ @update:open="(open) => handleSelectOpenChange(field, open)"
1024
+ >
1025
+ <PopoverAnchor as-child>
1026
+ <InputGroup :data-disabled="isFieldDisabled(field) ? 'true' : void 0">
1027
+ <PopoverTrigger as-child>
1028
+ <InputGroupInput
1029
+ :id="`${id}:${field.path}`"
1030
+ :model-value="getSelectDisplayValue(selectState, selectState.selectedKey)"
1031
+ :disabled="isFieldDisabled(field)"
1032
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1033
+ :placeholder="t('select-placeholder')"
1034
+ class="text-left"
1035
+ readonly
1036
+ @blur="handleSelectBlur(field)"
1037
+ />
1038
+ </PopoverTrigger>
1039
+ <InputGroupAddon v-if="field.icon">
1040
+ <Icon
1041
+ :icon="field.icon"
1042
+ />
1043
+ </InputGroupAddon>
1044
+ <InputGroupAddon
1045
+ v-if="hasProperty(modelValue, field.path)"
1046
+ align="inline-end"
1047
+ :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
1048
+ >
1049
+ <Tooltip :delay-duration="800">
1050
+ <TooltipTrigger>
1051
+ <InputGroupButton as-child>
1052
+ <button
1053
+ type="button"
1054
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
1055
+ :disabled="isFieldDisabled(field)"
1056
+ @click="clearSelectField(field)"
1057
+ >
1058
+ <Icon
1059
+ icon="fluent:dismiss-20-regular"
1060
+ />
1061
+ </button>
1062
+ </InputGroupButton>
1063
+ </TooltipTrigger>
1064
+ <TooltipContent>
1065
+ {{ t("clear") }}
1066
+ </TooltipContent>
1067
+ </Tooltip>
1068
+ </InputGroupAddon>
1069
+ </InputGroup>
1070
+ </PopoverAnchor>
1071
+
1072
+ <PopoverContent
1073
+ class="p-0"
1074
+ :style="{ width: 'var(--reka-popover-trigger-width)' }"
1075
+ >
1076
+ <Command
1077
+ :model-value="selectState.selectedKey"
1078
+ :disabled="isFieldDisabled(field)"
1079
+ selection-behavior="toggle"
1080
+ @update:model-value="(value) => handleSelectCommandValueChange(field, selectState, value)"
1081
+ >
1082
+ <CommandInput :placeholder="t('select-search-placeholder')" />
1083
+ <CommandList>
1084
+ <CommandEmpty as-child>
1085
+ <section class="h-32 flex flex-col text-lg items-center justify-center gap-2 select-none">
1086
+ <Icon
1087
+ icon="fluent:app-recent-20-regular"
1088
+ class="text-zinc-400 text-2xl!"
1089
+ />
1090
+ <p class="text-zinc-500">
1091
+ {{ t("select-empty") }}
1092
+ </p>
1093
+ </section>
1094
+ </CommandEmpty>
1095
+ <CommandGroup>
1096
+ <CommandItem
1097
+ v-for="option in selectState.options"
1098
+ :key="option.key"
1099
+ data-slot="select-option"
1100
+ :value="option.key"
1101
+ 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"
1102
+ >
1103
+ {{ option.label }}
1104
+ </CommandItem>
1105
+ </CommandGroup>
1106
+ </CommandList>
1107
+ </Command>
1108
+ </PopoverContent>
1109
+ </Popover>
1110
+ </template>
1111
+
1112
+ <template v-else-if="field.type === 'radio'">
1113
+ <RadioGroupRoot
1114
+ v-for="radioState in [getSelectFieldState(field)]"
1115
+ :key="`${field.id}:radio:${radioState.selectedKey ?? 'empty'}`"
1116
+ :model-value="radioState.selectedKey"
1117
+ :disabled="isFieldDisabled(field)"
1118
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1119
+ class="flex flex-wrap gap-x-4 gap-y-1.5"
1120
+ @update:model-value="(value) => handleSelectValueChange(field, radioState, value)"
1121
+ @focusout="validateField(field)"
1122
+ >
1123
+ <label
1124
+ v-for="option in radioState.options"
1125
+ :key="option.key"
1126
+ class="flex items-center gap-1.5 text-sm cursor-pointer data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
1127
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
1128
+ >
1129
+ <RadioGroupItem
1130
+ :value="option.key"
1131
+ data-slot="radio-group-item"
1132
+ 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"
1133
+ >
1134
+ <RadioGroupIndicator class="flex items-center justify-center">
1135
+ <span class="size-1.5 rounded-full bg-white" />
1136
+ </RadioGroupIndicator>
1137
+ </RadioGroupItem>
1138
+ {{ option.label }}
1139
+ </label>
1140
+ </RadioGroupRoot>
1141
+ </template>
1142
+
1143
+ <InputGroup
1144
+ v-else
1145
+ :data-disabled="isFieldDisabled(field) ? 'true' : void 0"
1146
+ :class="field.type === 'textarea' ? 'h-auto flex-col items-stretch' : void 0"
1147
+ >
1148
+ <div
1149
+ v-if="field.type === 'textarea'"
1150
+ class="flex min-w-0 w-full items-center"
1151
+ >
1152
+ <InputGroupTextarea
1153
+ :id="`${id}:${field.path}`"
1154
+ :model-value="getProperty(modelValue, field.path)"
1155
+ :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1156
+ :disabled="isFieldDisabled(field)"
1157
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1158
+ @update:model-value="(value) => {
1159
+ if (!value && !field.discardEmptyString) {
1160
+ deleteProperty(modelValue, field.path);
1161
+ } else {
1162
+ setProperty(modelValue, field.path, value);
1163
+ }
1164
+ }"
1165
+ @blur="validateField(field)"
1166
+ />
1167
+ <InputGroupAddon v-if="field.icon">
1168
+ <Icon
1169
+ :icon="field.icon"
1170
+ />
1171
+ </InputGroupAddon>
1172
+ </div>
1173
+ <InputGroupInput
1174
+ v-if="field.type === 'string'"
1175
+ :id="`${id}:${field.path}`"
1176
+ :treat-empty-as-different-state-from-null="!!field.discardEmptyString"
1177
+ :model-value="getProperty(modelValue, field.path)"
1178
+ :maxlength="field.maxLength ? $dsl.evaluate`${field.maxLength}`() : void 0"
1179
+ :disabled="isFieldDisabled(field)"
1180
+ :aria-invalid="isFieldInvalid(field) ? 'true' : void 0"
1181
+ @update:model-value="(value) => {
1182
+ if (!value && !field.discardEmptyString) {
1183
+ deleteProperty(modelValue, field.path);
1184
+ } else {
1185
+ setProperty(modelValue, field.path, value);
1186
+ }
1187
+ }"
1188
+ @blur="validateField(field)"
1189
+ />
1190
+ <InputGroupNumberField
1191
+ v-if="field.type === 'number'"
1192
+ :id="`${id}:${field.path}`"
1193
+ :model-value="getProperty(modelValue, field.path) ?? null"
1194
+ :min="field.min ? $dsl.evaluate`${field.min}`() : void 0"
1195
+ :max="field.max ? $dsl.evaluate`${field.max}`() : void 0"
1196
+ :step="field.step ? $dsl.evaluate`${field.step}`() : void 0"
1197
+ :disabled="isFieldDisabled(field)"
1198
+ :invalid="isFieldInvalid(field)"
1199
+ @update:model-value="(value) => {
1200
+ if (!value && value !== 0) {
1201
+ deleteProperty(modelValue, field.path);
1202
+ } else {
1203
+ setProperty(modelValue, field.path, value);
1204
+ }
1205
+ }"
1206
+ @blur="validateField(field)"
1207
+ />
1208
+ <InputGroupAddon v-if="field.type !== 'textarea' && field.icon">
1209
+ <Icon
1210
+ :icon="field.icon"
1211
+ />
1212
+ </InputGroupAddon>
1213
+ <InputGroupAddon
1214
+ v-if="field.type !== 'textarea' && hasProperty(modelValue, field.path)"
1215
+ align="inline-end"
1216
+ :class="getConfigOrientation(displayConfig) === 'floating' ? 'group-data-[disabled=true]/input-group:hidden' : void 0"
1217
+ >
1218
+ <Tooltip :delay-duration="800">
1219
+ <TooltipTrigger>
1220
+ <InputGroupButton as-child>
1221
+ <button
1222
+ type="button"
1223
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
1224
+ :disabled="isFieldDisabled(field)"
1225
+ @click="deleteProperty(modelValue, field.path)"
1226
+ >
1227
+ <Icon
1228
+ icon="fluent:dismiss-20-regular"
1229
+ />
1230
+ </button>
1231
+ </InputGroupButton>
1232
+ </TooltipTrigger>
1233
+ <TooltipContent>
1234
+ {{ t("clear") }}
1235
+ </TooltipContent>
1236
+ </Tooltip>
1237
+ </InputGroupAddon>
1238
+ <InputGroupAddon
1239
+ v-if="field.type === 'string' && field.maxLength && getProperty(modelValue, field.path)"
1240
+ align="inline-end"
1241
+ >
1242
+ <span class="text-xs text-zinc-400 font-mono">
1243
+ <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1244
+ </span>
1245
+ </InputGroupAddon>
1246
+ <InputGroupAddon
1247
+ v-if="field.type === 'textarea' && (hasProperty(modelValue, field.path) || field.maxLength && getProperty(modelValue, field.path))"
1248
+ align="block-end"
1249
+ >
1250
+ <Tooltip
1251
+ v-if="hasProperty(modelValue, field.path)"
1252
+ :delay-duration="800"
1253
+ >
1254
+ <TooltipTrigger>
1255
+ <InputGroupButton as-child>
1256
+ <button
1257
+ type="button"
1258
+ class="text-zinc-300 hover:text-zinc-500 transition-colors"
1259
+ :disabled="isFieldDisabled(field)"
1260
+ @click="deleteProperty(modelValue, field.path)"
1261
+ >
1262
+ <Icon
1263
+ icon="fluent:dismiss-20-regular"
1264
+ />
1265
+ </button>
1266
+ </InputGroupButton>
1267
+ </TooltipTrigger>
1268
+ <TooltipContent>
1269
+ {{ t("clear") }}
1270
+ </TooltipContent>
1271
+ </Tooltip>
1272
+ <span
1273
+ v-if="field.maxLength && getProperty(modelValue, field.path)"
1274
+ class="text-xs text-zinc-400 font-mono"
1275
+ >
1276
+ <span class="inline-block text-right">{{ String(getProperty(modelValue, field.path) ?? "").length }}</span>/{{ field.maxLength }}
1277
+ </span>
1278
+ </InputGroupAddon>
1279
+ </InputGroup>
1280
+ </template>
1281
+
1282
+ <FieldError
1283
+ v-if="isFieldInvalid(field)"
1284
+ :class="usesContentsOrientation(displayConfig) ? 'static pt-1' : void 0"
1285
+ >
1286
+ <span v-html="renderValidationMessage(field)" />
1287
+ </FieldError>
1288
+ </FieldContent>
1289
+ </Field>
825
1290
  </template>
826
- </FieldsBody>
1291
+ </component>
827
1292
 
828
1293
  <slot />
829
1294
  </div>