@shwfed/nuxt 0.11.47 → 0.11.48

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.
@@ -6,21 +6,15 @@ 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, hasProperty, setProperty } from "dot-prop";
9
+ import { deleteProperty, getProperty, 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";
14
13
  import { mergeDslContexts, useCELContext } from "../../../plugins/cel/context";
15
- import { Calendar } from "../calendar";
16
14
  import { Button } from "../button";
17
- import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../command";
18
- import { Field, FieldContent, FieldError, FieldLabel, FieldSet } from "../field";
15
+ import FieldsBody from "./FieldsBody.vue";
19
16
  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";
22
17
  import { Skeleton } from "../skeleton";
23
- import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip";
24
18
  import { getLocalizedText } from "../../../utils/coders";
25
19
  import { FieldsConfigC, createFieldsConfig } from "./schema";
26
20
  const id = useId();
@@ -31,7 +25,7 @@ const defaultConfig = createFieldsConfig({
31
25
  const props = defineProps({
32
26
  config: { type: null, required: true }
33
27
  });
34
- defineSlots();
28
+ const slots = defineSlots();
35
29
  const emit = defineEmits(["update:config", "initial-value-ready"]);
36
30
  const config = computedAsync(async () => FieldsConfigC.parse(await props.config.pipe(Effect.runPromise) ?? defaultConfig));
37
31
  const { t, locale } = useI18n();
@@ -50,35 +44,23 @@ const isReady = ref(false);
50
44
  const hasInitializedFieldValues = ref(false);
51
45
  const hasEmittedInitialValueReady = ref(false);
52
46
  const readyResolvers = [];
53
- function cloneConfig(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;
47
+ function requireSlotProps(slotProps) {
48
+ if (!slotProps) {
49
+ throw new TypeError("missing slot props");
68
50
  }
69
- if (config2.style) {
70
- nextConfig.style = config2.style;
71
- }
72
- return nextConfig;
51
+ return slotProps;
52
+ }
53
+ function cloneConfig(config2) {
54
+ return structuredClone(toRaw(config2));
73
55
  }
74
- function getConfigOrientation(config2) {
75
- return config2.orientation ?? "horizontal";
56
+ function getBodyOrientation(body) {
57
+ return body.orientation ?? "horizontal";
76
58
  }
77
- function usesContentsOrientation(config2) {
78
- return getConfigOrientation(config2) === "contents";
59
+ function usesContentsOrientation(body) {
60
+ return getBodyOrientation(body) === "contents";
79
61
  }
80
- function isConfigBordered(config2) {
81
- return usesContentsOrientation(config2) && config2.bordered === true;
62
+ function isBodyBordered(body) {
63
+ return usesContentsOrientation(body) && body.bordered === true;
82
64
  }
83
65
  function tryEvaluateExpression(source, context) {
84
66
  try {
@@ -99,12 +81,12 @@ function evaluateExpression(source, context, fallback) {
99
81
  }
100
82
  return result.value;
101
83
  }
102
- function getConfigStyle(config2) {
84
+ function getBodyStyle(body) {
103
85
  const style = {};
104
- if (!config2.style) {
86
+ if (!body.style) {
105
87
  return style;
106
88
  }
107
- const styleMap = evaluateExpression(config2.style, { form: modelValue.value }, {});
89
+ const styleMap = evaluateExpression(body.style, { form: modelValue.value }, {});
108
90
  if (!styleMap || typeof styleMap !== "object" || Array.isArray(styleMap)) {
109
91
  return style;
110
92
  }
@@ -134,8 +116,8 @@ function getFieldPartStyle(styleExpression) {
134
116
  function getFieldStyle(field) {
135
117
  return getFieldPartStyle(field.style);
136
118
  }
137
- function getGroupStyle(group, config2) {
138
- const style = usesContentsOrientation(config2) ? getConfigStyle(config2) : {};
119
+ function getGroupStyle(group, body) {
120
+ const style = usesContentsOrientation(body) ? getBodyStyle(body) : {};
139
121
  const groupStyle = group.style ? evaluateExpression(group.style, { form: modelValue.value, id: group.id }, {}) : {};
140
122
  if (!groupStyle || typeof groupStyle !== "object" || Array.isArray(groupStyle)) {
141
123
  return style;
@@ -147,42 +129,57 @@ function getGroupStyle(group, config2) {
147
129
  }
148
130
  return style;
149
131
  }
150
- function getConfigEntries(config2) {
132
+ function getBodyEntries(body) {
151
133
  return [
152
- ...config2.fields.map((field) => ({
134
+ ...body.fields.map((field) => ({
153
135
  key: `field:${field.id}`,
154
136
  field
155
137
  })),
156
- ...config2.groups.map((group) => ({
138
+ ...(body.groups ?? []).map((group) => ({
157
139
  key: `group:${group.id}`,
158
140
  group
159
141
  }))
160
142
  ];
161
143
  }
162
- function getConfigFields(config2) {
163
- return [...config2.fields, ...config2.groups.flatMap((group) => group.fields)];
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
+ }
164
158
  }
165
- function getFieldContainerStyle(field, config2) {
166
- if (!isPassiveField(field) && usesContentsOrientation(config2)) {
159
+ function getFieldContainerStyle(field, body) {
160
+ if (field.type === "slot" || field.type === "container") {
161
+ return {};
162
+ }
163
+ if (!isPassiveField(field) && usesContentsOrientation(body)) {
167
164
  return {};
168
165
  }
169
166
  return getFieldStyle(field);
170
167
  }
171
- function getFieldLabelStyle(field, config2) {
172
- if (!usesContentsOrientation(config2)) {
168
+ function getFieldLabelStyle(field, body) {
169
+ if (!usesContentsOrientation(body)) {
173
170
  return {};
174
171
  }
175
172
  return getFieldPartStyle(field.labelStyle);
176
173
  }
177
- function getFieldContentStyle(field, config2) {
178
- const style = usesContentsOrientation(config2) ? getFieldPartStyle(field.contentStyle) : {};
179
- if (usesContentsOrientation(config2) && isFieldLabelHidden(field) && style.gridColumn === void 0) {
174
+ function getFieldContentStyle(field, body) {
175
+ const style = usesContentsOrientation(body) ? getFieldPartStyle(field.contentStyle) : {};
176
+ if (usesContentsOrientation(body) && isFieldLabelHidden(field) && style.gridColumn === void 0) {
180
177
  style.gridColumn = "1 / -1";
181
178
  }
182
179
  return style;
183
180
  }
184
- function getMarkdownBodyContentStyle(field, config2) {
185
- if (!usesContentsOrientation(config2)) {
181
+ function getMarkdownBodyContentStyle(field, body) {
182
+ if (!usesContentsOrientation(body)) {
186
183
  return {};
187
184
  }
188
185
  return {
@@ -193,11 +190,8 @@ function getMarkdownBodyContentStyle(field, config2) {
193
190
  function isPassiveField(field) {
194
191
  return field.type === "slot" || field.type === "empty";
195
192
  }
196
- function isMarkdownDisplayField(field) {
197
- return field.type === "markdown" || field.type === "markdown-body";
198
- }
199
193
  function isInteractiveField(field) {
200
- return !isMarkdownDisplayField(field);
194
+ return field.type !== "slot" && field.type !== "empty" && field.type !== "markdown" && field.type !== "markdown-body" && field.type !== "container";
201
195
  }
202
196
  function isFieldLabelHidden(field) {
203
197
  return field.hideLabel === true;
@@ -225,13 +219,13 @@ function getFieldValue(field) {
225
219
  return getProperty(modelValue.value, field.path);
226
220
  }
227
221
  function initializeFieldValues(config2) {
228
- for (const field of getConfigFields(config2)) {
229
- if (isPassiveField(field) || isMarkdownDisplayField(field) || !field.initialValue) {
230
- continue;
222
+ visitBodyFields(config2, (field) => {
223
+ if (!isInteractiveField(field) || !field.initialValue) {
224
+ return;
231
225
  }
232
226
  const initialValueResult = tryEvaluateExpression(field.initialValue, { form: modelValue.value });
233
227
  if (!initialValueResult.ok) {
234
- continue;
228
+ return;
235
229
  }
236
230
  const initialValue = initialValueResult.value;
237
231
  setProperty(
@@ -239,7 +233,7 @@ function initializeFieldValues(config2) {
239
233
  field.path,
240
234
  field.type === "number" && typeof initialValue === "bigint" ? Number(initialValue) : initialValue
241
235
  );
242
- }
236
+ });
243
237
  }
244
238
  const MIME_LABELS = {
245
239
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel",
@@ -544,15 +538,15 @@ function validateField(field) {
544
538
  }
545
539
  function validateFields() {
546
540
  const nextValidationErrors = {};
547
- for (const field of getConfigFields(displayConfig.value)) {
548
- if (isPassiveField(field) || isMarkdownDisplayField(field)) {
549
- continue;
541
+ visitBodyFields(displayConfig.value, (field, visible) => {
542
+ if (!visible || !isInteractiveField(field)) {
543
+ return;
550
544
  }
551
545
  const failure = getValidationFailure(field);
552
546
  if (failure) {
553
547
  nextValidationErrors[field.path] = failure;
554
548
  }
555
- }
549
+ });
556
550
  validationErrors.value = nextValidationErrors;
557
551
  return Object.keys(nextValidationErrors).length === 0;
558
552
  }
@@ -563,7 +557,10 @@ function getFieldLabel(field) {
563
557
  return getLocalizedText(field.title, locale.value) ?? field.path;
564
558
  }
565
559
  function getDisplayFieldLabel(field) {
566
- return getLocalizedText(field.title, locale.value) ?? field.id;
560
+ if ("title" in field && field.title) {
561
+ return getLocalizedText(field.title, locale.value) ?? field.id;
562
+ }
563
+ return field.id;
567
564
  }
568
565
  function isFieldRequired(field) {
569
566
  return field.required === true;
@@ -615,6 +612,30 @@ function handleCalendarBlur(field) {
615
612
  }
616
613
  }, 0);
617
614
  }
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
+ }
618
639
  function handleConfiguratorConfirm(nextConfig) {
619
640
  displayConfig.value = cloneConfig(nextConfig);
620
641
  emit("update:config", nextConfig);
@@ -646,11 +667,11 @@ watch(config, (value) => {
646
667
  }, { immediate: true });
647
668
  watchEffect(() => {
648
669
  const activePaths = /* @__PURE__ */ new Set();
649
- for (const field of getConfigFields(displayConfig.value)) {
650
- if (!isPassiveField(field) && !isFieldHidden(field) && !isMarkdownDisplayField(field) && !isFieldDisabled(field)) {
670
+ visitBodyFields(displayConfig.value, (field, visible) => {
671
+ if (visible && isInteractiveField(field) && !isFieldDisabled(field)) {
651
672
  activePaths.add(field.path);
652
673
  }
653
- }
674
+ });
654
675
  for (const path of Object.keys(validationErrors.value)) {
655
676
  if (!activePaths.has(path)) {
656
677
  clearFieldValidation(path);
@@ -667,11 +688,71 @@ watchEffect(() => {
667
688
  }
668
689
  }
669
690
  });
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
+ }));
670
750
  </script>
671
751
 
672
752
  <script>
673
753
  export {
674
754
  CalendarFieldC,
755
+ ContainerFieldC,
675
756
  CURRENT_COMPATIBILITY_DATE,
676
757
  EmptyFieldC,
677
758
  FieldC,
@@ -688,6 +769,7 @@ export {
688
769
  SlotFieldC,
689
770
  SUPPORTED_COMPATIBILITY_DATES,
690
771
  StringFieldC,
772
+ TextareaFieldC,
691
773
  UploadFieldC,
692
774
  ValidationRuleC,
693
775
  createFieldsConfig,
@@ -700,9 +782,9 @@ export {
700
782
  :class="[
701
783
  'relative p-1 -m-1 border border-dashed',
702
784
  isCheating ? 'border-(--primary)/20 rounded hover:border-(--primary)/40 transition-colors duration-150 group cursor-pointer' : 'border-transparent',
703
- isConfigBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
785
+ isBodyBordered(displayConfig) ? '!p-0 !m-0 border-transparent' : ''
704
786
  ]"
705
- :style="getConfigStyle(displayConfig)"
787
+ :style="getBodyStyle(displayConfig)"
706
788
  >
707
789
  <Button
708
790
  v-if="isCheating"
@@ -728,567 +810,20 @@ export {
728
810
  class="absolute inset-0 z-10 w-full h-full"
729
811
  />
730
812
 
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' }"
813
+ <FieldsBody
814
+ :body="displayConfig"
815
+ :renderer="renderContext"
741
816
  >
742
817
  <template
743
- v-for="field in entry.group ? entry.group.fields : entry.field ? [entry.field] : []"
744
- :key="field.id"
818
+ v-for="(_, slotName) in slots"
819
+ #[slotName]="slotProps"
745
820
  >
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>
758
821
  <slot
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)"
822
+ :name="slotName"
823
+ v-bind="requireSlotProps(slotProps)"
769
824
  />
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>
1290
825
  </template>
1291
- </component>
826
+ </FieldsBody>
1292
827
 
1293
828
  <slot />
1294
829
  </div>