@evanschleret/formforgeclient 1.2.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/module.cjs +1 -0
- package/dist/module.d.cts +1 -0
- package/dist/module.d.mts +1 -0
- package/dist/module.d.ts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/api/client.js +4 -2
- package/dist/runtime/api/request.d.ts +1 -0
- package/dist/runtime/api/schema.js +4 -4
- package/dist/runtime/assets/formforge.css +1 -0
- package/dist/runtime/composables/index.d.ts +1 -1
- package/dist/runtime/composables/useFormForgeBuilder.d.ts +24 -2
- package/dist/runtime/composables/useFormForgeBuilder.js +299 -43
- package/dist/runtime/composables/useFormForgeForm.js +15 -5
- package/dist/runtime/composables/useFormForgeI18n.d.ts +245 -19
- package/dist/runtime/composables/useFormForgeI18n.js +245 -19
- package/dist/runtime/composables/useFormForgeSubmit.js +31 -9
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/renderers/default/FormForgeBuilder.d.vue.ts +21 -2
- package/dist/runtime/renderers/default/FormForgeBuilder.vue +689 -738
- package/dist/runtime/renderers/default/FormForgeBuilder.vue.d.ts +21 -2
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.d.vue.ts +17 -0
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue +32 -0
- package/dist/runtime/renderers/default/FormForgeBuilderBlockSettingsModal.vue.d.ts +17 -0
- package/dist/runtime/renderers/default/FormForgeRenderer.d.vue.ts +3 -4
- package/dist/runtime/renderers/default/FormForgeRenderer.vue +344 -294
- package/dist/runtime/renderers/default/FormForgeRenderer.vue.d.ts +3 -4
- package/dist/runtime/renderers/default/FormForgeRendererField.d.vue.ts +22 -0
- package/dist/runtime/renderers/default/FormForgeRendererField.vue +237 -0
- package/dist/runtime/renderers/default/FormForgeRendererField.vue.d.ts +22 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.d.vue.ts +18 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.vue +31 -0
- package/dist/runtime/renderers/default/FormForgeRendererPage.vue.d.ts +18 -0
- package/dist/runtime/renderers/default/FormForgeResponse.vue +4 -3
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue +118 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderAddressFieldsCard.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.d.vue.ts +46 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue +205 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderBlockCard.vue.d.ts +46 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue +37 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceDisplayField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue +195 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderChoiceOptionsField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.d.vue.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue +91 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderDescriptionField.vue.d.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.d.vue.ts +13 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue +387 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderLogicPanel.vue.d.ts +13 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.d.vue.ts +44 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue +328 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderQuestionRow.vue.d.ts +44 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.d.vue.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue +47 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderTemporalModeField.vue.d.ts +11 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.d.vue.ts +14 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue +595 -0
- package/dist/runtime/renderers/default/builder/FormForgeBuilderValidationRulesSection.vue.d.ts +14 -0
- package/dist/runtime/renderers/default/builder/builderFieldHelpers.d.ts +3 -0
- package/dist/runtime/renderers/default/builder/builderFieldHelpers.js +4 -0
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/management.d.ts +12 -0
- package/dist/runtime/types/schema.d.ts +72 -4
- package/dist/runtime/utils/defaults.d.ts +7 -0
- package/dist/runtime/utils/defaults.js +86 -0
- package/dist/runtime/utils/page-logic.d.ts +24 -0
- package/dist/runtime/utils/page-logic.js +351 -0
- package/dist/runtime/utils/rich-text.d.ts +3 -0
- package/dist/runtime/utils/rich-text.js +72 -0
- package/dist/runtime/utils/schema.d.ts +1 -1
- package/dist/runtime/utils/schema.js +70 -16
- package/dist/runtime/utils/temporal.d.ts +10 -0
- package/dist/runtime/utils/temporal.js +28 -0
- package/dist/runtime/utils/validation.d.ts +5 -0
- package/dist/runtime/utils/validation.js +36 -0
- package/dist/runtime/validation/zod.d.ts +5 -2
- package/dist/runtime/validation/zod.js +563 -54
- package/dist/types.d.mts +2 -0
- package/package.json +18 -14
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, useTemplateRef, watch } from "#imports";
|
|
3
|
-
import {
|
|
4
|
-
import UCheckbox from "@nuxt/ui/components/Checkbox.vue";
|
|
5
|
-
import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
|
|
6
|
-
import UFileUpload from "@nuxt/ui/components/FileUpload.vue";
|
|
3
|
+
import { parseDate, parseTime } from "@internationalized/date";
|
|
7
4
|
import UInput from "@nuxt/ui/components/Input.vue";
|
|
8
|
-
import UInputDate from "@nuxt/ui/components/InputDate.vue";
|
|
9
|
-
import UInputNumber from "@nuxt/ui/components/InputNumber.vue";
|
|
10
|
-
import UInputTime from "@nuxt/ui/components/InputTime.vue";
|
|
11
5
|
import UProgress from "@nuxt/ui/components/Progress.vue";
|
|
12
|
-
import URadioGroup from "@nuxt/ui/components/RadioGroup.vue";
|
|
13
|
-
import USelect from "@nuxt/ui/components/Select.vue";
|
|
14
|
-
import USelectMenu from "@nuxt/ui/components/SelectMenu.vue";
|
|
15
|
-
import UStepper from "@nuxt/ui/components/Stepper.vue";
|
|
16
|
-
import USwitch from "@nuxt/ui/components/Switch.vue";
|
|
17
|
-
import UTextarea from "@nuxt/ui/components/Textarea.vue";
|
|
18
6
|
import { isFormForgeJsonObject } from "../../utils/object";
|
|
7
|
+
import { createDefaultAddressFields } from "../../utils/defaults";
|
|
8
|
+
import { resolveTemporalMode } from "../../utils/temporal";
|
|
19
9
|
import { sanitizePayloadWithSchema } from "../../utils/renderer-payload";
|
|
20
10
|
import { useFormForgeForm } from "../../composables/useFormForgeForm";
|
|
21
11
|
import { useFormForgeI18n } from "../../composables/useFormForgeI18n";
|
|
22
12
|
import { useFormForgeSubmit } from "../../composables/useFormForgeSubmit";
|
|
13
|
+
import { createFormForgeZodSchema } from "../../validation/zod";
|
|
14
|
+
import {
|
|
15
|
+
evaluatePageLogicRule,
|
|
16
|
+
getPageLogic,
|
|
17
|
+
resolvePageLogicJumpTarget
|
|
18
|
+
} from "../../utils/page-logic";
|
|
19
|
+
import FormForgeRendererField from "./FormForgeRendererField.vue";
|
|
20
|
+
import FormForgeRendererPage from "./FormForgeRendererPage.vue";
|
|
23
21
|
const props = defineProps({
|
|
24
22
|
schema: { type: Object, required: false, default: void 0 },
|
|
25
23
|
modelValue: { type: Object, required: false, default: void 0 },
|
|
@@ -37,12 +35,12 @@ const props = defineProps({
|
|
|
37
35
|
uploadMode: { type: String, required: false, default: void 0 },
|
|
38
36
|
clearAfterSubmit: { type: Boolean, required: false, default: false },
|
|
39
37
|
showProgress: { type: Boolean, required: false, default: false },
|
|
40
|
-
progressVariant: { type: String, required: false, default: "stepper" },
|
|
41
38
|
showAlertOnError: { type: Boolean, required: false, default: false },
|
|
42
39
|
validateOn: { type: Array, required: false, default: void 0 },
|
|
43
|
-
validateOnBlur: { type: Boolean, required: false, default: void 0 }
|
|
40
|
+
validateOnBlur: { type: Boolean, required: false, default: void 0 },
|
|
41
|
+
previewPageKey: { type: [String, null], required: false, default: null }
|
|
44
42
|
});
|
|
45
|
-
const { t } = useFormForgeI18n({
|
|
43
|
+
const { t, locale } = useFormForgeI18n({
|
|
46
44
|
locale: () => props.clientConfig?.locale
|
|
47
45
|
});
|
|
48
46
|
const emit = defineEmits(["update:modelValue", "submit", "submitted", "error"]);
|
|
@@ -106,7 +104,7 @@ const internalSubmit = useFormForgeSubmit({
|
|
|
106
104
|
key: internalFormKey.value === "" ? "__missing_form_key__" : internalFormKey.value,
|
|
107
105
|
version: props.formVersion,
|
|
108
106
|
endpoint: props.endpoint,
|
|
109
|
-
schema: () =>
|
|
107
|
+
schema: () => getResolvedSchema(),
|
|
110
108
|
state: () => internalForm.state.value,
|
|
111
109
|
clientConfig: props.clientConfig
|
|
112
110
|
});
|
|
@@ -134,7 +132,13 @@ function getResolvedSchema() {
|
|
|
134
132
|
}
|
|
135
133
|
function getResolvedZodSchema() {
|
|
136
134
|
if (usesExternalSchema.value) {
|
|
137
|
-
|
|
135
|
+
const schema = unwrapSchemaProp(props.schema);
|
|
136
|
+
if (schema === null) {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
return unwrapZodSchemaProp(props.zodSchema) ?? createFormForgeZodSchema(schema, {
|
|
140
|
+
locale: locale.value
|
|
141
|
+
});
|
|
138
142
|
}
|
|
139
143
|
return internalForm.zodSchema.value;
|
|
140
144
|
}
|
|
@@ -160,6 +164,32 @@ const formState = computed({
|
|
|
160
164
|
internalForm.replaceState(value);
|
|
161
165
|
}
|
|
162
166
|
});
|
|
167
|
+
const submissionCode = ref("");
|
|
168
|
+
const isPreviewMode = computed(() => {
|
|
169
|
+
return typeof props.previewPageKey === "string" && props.previewPageKey.trim() !== "";
|
|
170
|
+
});
|
|
171
|
+
const requiresSubmissionCode = computed(() => {
|
|
172
|
+
const schema = getResolvedSchema();
|
|
173
|
+
if (schema === null) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return schema.submission_code_required === true;
|
|
177
|
+
});
|
|
178
|
+
const availabilityAlert = computed(() => {
|
|
179
|
+
const schema = getResolvedSchema();
|
|
180
|
+
if (schema === null) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const publishAt = typeof schema.publish_at === "string" ? Date.parse(schema.publish_at) : Number.NaN;
|
|
184
|
+
if (!Number.isNaN(publishAt) && publishAt > Date.now()) {
|
|
185
|
+
return t("renderer.formNotAvailableYet");
|
|
186
|
+
}
|
|
187
|
+
const pauseAt = typeof schema.pause_at === "string" ? Date.parse(schema.pause_at) : Number.NaN;
|
|
188
|
+
if (!Number.isNaN(pauseAt) && pauseAt <= Date.now()) {
|
|
189
|
+
return t("renderer.formPaused");
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
});
|
|
163
193
|
watch(
|
|
164
194
|
() => [usesExternalModel.value, getResolvedSchema(), unwrapModelValueProp(props.modelValue)],
|
|
165
195
|
([externalModel, schema, modelValue]) => {
|
|
@@ -200,6 +230,26 @@ const displayPages = computed(() => {
|
|
|
200
230
|
}
|
|
201
231
|
];
|
|
202
232
|
});
|
|
233
|
+
const previewPage = computed(() => {
|
|
234
|
+
if (!isPreviewMode.value) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const pageKey = props.previewPageKey?.trim();
|
|
238
|
+
if (typeof pageKey !== "string" || pageKey === "") {
|
|
239
|
+
return displayPages.value[0] ?? null;
|
|
240
|
+
}
|
|
241
|
+
return displayPages.value.find((page) => page.page_key === pageKey) ?? displayPages.value[0] ?? null;
|
|
242
|
+
});
|
|
243
|
+
const pageLogicByKey = computed(() => {
|
|
244
|
+
const map = {};
|
|
245
|
+
for (const page of displayPages.value) {
|
|
246
|
+
map[page.page_key] = getPageLogic(page);
|
|
247
|
+
}
|
|
248
|
+
return map;
|
|
249
|
+
});
|
|
250
|
+
const hasPageLogicRules = computed(() => {
|
|
251
|
+
return Object.values(pageLogicByKey.value).some((logic) => logic.rules.length > 0);
|
|
252
|
+
});
|
|
203
253
|
const fieldsByKey = computed(() => {
|
|
204
254
|
const map = {};
|
|
205
255
|
for (const page of displayPages.value) {
|
|
@@ -233,7 +283,7 @@ function isEmptyValue(value) {
|
|
|
233
283
|
const rangeValue = value;
|
|
234
284
|
return isEmptyValue(rangeValue.start) && isEmptyValue(rangeValue.end);
|
|
235
285
|
}
|
|
236
|
-
return Object.
|
|
286
|
+
return Object.values(value).every((entry) => isEmptyValue(entry));
|
|
237
287
|
}
|
|
238
288
|
return false;
|
|
239
289
|
}
|
|
@@ -407,7 +457,30 @@ const runtimeConditions = computed(() => {
|
|
|
407
457
|
};
|
|
408
458
|
}
|
|
409
459
|
}
|
|
410
|
-
|
|
460
|
+
const requiredByLogic = /* @__PURE__ */ new Set();
|
|
461
|
+
if (schema === null || hasPageLogicRules.value || !Array.isArray(schema.conditions)) {
|
|
462
|
+
for (const [pageKey, logic] of Object.entries(pageLogicByKey.value)) {
|
|
463
|
+
const page = displayPages.value.find((item) => item.page_key === pageKey);
|
|
464
|
+
if (page === void 0) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
for (const rule of logic.rules) {
|
|
468
|
+
if (!evaluatePageLogicRule(rule, page, (field) => formState.value[field.name])) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
for (const thenAction of rule.then) {
|
|
472
|
+
if (thenAction.action === "require" && typeof thenAction.field_key === "string" && thenAction.field_key !== "") {
|
|
473
|
+
requiredByLogic.add(thenAction.field_key);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
for (const fieldKey of requiredByLogic) {
|
|
479
|
+
const fieldState = fieldsState[fieldKey];
|
|
480
|
+
if (fieldState !== void 0) {
|
|
481
|
+
fieldState.required = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
411
484
|
return {
|
|
412
485
|
pages: pagesState,
|
|
413
486
|
fields: fieldsState
|
|
@@ -479,6 +552,28 @@ const runtimeConditions = computed(() => {
|
|
|
479
552
|
fieldState.required = true;
|
|
480
553
|
}
|
|
481
554
|
}
|
|
555
|
+
for (const [pageKey, logic] of Object.entries(pageLogicByKey.value)) {
|
|
556
|
+
const page = displayPages.value.find((item) => item.page_key === pageKey);
|
|
557
|
+
if (page === void 0) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
for (const rule of logic.rules) {
|
|
561
|
+
if (!evaluatePageLogicRule(rule, page, (field) => formState.value[field.name])) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
for (const thenAction of rule.then) {
|
|
565
|
+
if (thenAction.action === "require" && typeof thenAction.field_key === "string" && thenAction.field_key !== "") {
|
|
566
|
+
requiredByLogic.add(thenAction.field_key);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
for (const fieldKey of requiredByLogic) {
|
|
572
|
+
const fieldState = fieldsState[fieldKey];
|
|
573
|
+
if (fieldState !== void 0) {
|
|
574
|
+
fieldState.required = true;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
482
577
|
return {
|
|
483
578
|
pages: pagesState,
|
|
484
579
|
fields: fieldsState
|
|
@@ -497,6 +592,26 @@ const pageKeyByFieldName = computed(() => {
|
|
|
497
592
|
return map;
|
|
498
593
|
});
|
|
499
594
|
const activePageKey = ref(null);
|
|
595
|
+
const isPageTransitioning = ref(false);
|
|
596
|
+
watch(
|
|
597
|
+
() => [isPreviewMode.value, props.previewPageKey, displayPages.value.map((page) => page.page_key)],
|
|
598
|
+
([previewMode, previewPageKey, pageKeys]) => {
|
|
599
|
+
if (!previewMode) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const pageKey = typeof previewPageKey === "string" ? previewPageKey.trim() : "";
|
|
603
|
+
if (pageKey !== "" && pageKeys.includes(pageKey)) {
|
|
604
|
+
activePageKey.value = pageKey;
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (pageKeys.length > 0 && (activePageKey.value === null || !pageKeys.includes(activePageKey.value))) {
|
|
608
|
+
activePageKey.value = pageKeys[0];
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
immediate: true
|
|
613
|
+
}
|
|
614
|
+
);
|
|
500
615
|
watch(
|
|
501
616
|
() => visiblePages.value.map((page) => page.page_key),
|
|
502
617
|
(pageKeys) => {
|
|
@@ -527,17 +642,7 @@ const activePageIndex = computed(() => {
|
|
|
527
642
|
return index;
|
|
528
643
|
});
|
|
529
644
|
const shouldShowProgress = computed(() => {
|
|
530
|
-
return props.showProgress && visiblePages.value.length > 1;
|
|
531
|
-
});
|
|
532
|
-
const stepperItems = computed(() => {
|
|
533
|
-
return visiblePages.value.map((page, index) => ({
|
|
534
|
-
title: page.title !== "" ? page.title : t("renderer.pageTitle", { index: index + 1 }),
|
|
535
|
-
description: typeof page.description === "string" && page.description.trim() !== "" ? page.description : void 0,
|
|
536
|
-
value: index + 1
|
|
537
|
-
}));
|
|
538
|
-
});
|
|
539
|
-
const pagedMode = computed(() => {
|
|
540
|
-
return shouldShowProgress.value;
|
|
645
|
+
return props.showProgress && visiblePages.value.length > 1 && (!isPreviewMode.value || props.simulation);
|
|
541
646
|
});
|
|
542
647
|
const currentVisiblePage = computed(() => {
|
|
543
648
|
if (visiblePages.value.length === 0) {
|
|
@@ -546,9 +651,57 @@ const currentVisiblePage = computed(() => {
|
|
|
546
651
|
const page = visiblePages.value[activePageIndex.value];
|
|
547
652
|
return page ?? null;
|
|
548
653
|
});
|
|
654
|
+
const progressValue = computed(() => {
|
|
655
|
+
if (visiblePages.value.length === 0) {
|
|
656
|
+
return 0;
|
|
657
|
+
}
|
|
658
|
+
return (activePageIndex.value + 1) / visiblePages.value.length * 100;
|
|
659
|
+
});
|
|
660
|
+
const shouldShowNavigation = computed(() => {
|
|
661
|
+
return visiblePages.value.length > 1 && (!isPreviewMode.value || props.simulation);
|
|
662
|
+
});
|
|
663
|
+
function resolveNextPageKey() {
|
|
664
|
+
const currentKey = activePageKey.value;
|
|
665
|
+
if (currentKey === null) {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
const currentDisplayIndex = displayPages.value.findIndex((page) => page.page_key === currentKey);
|
|
669
|
+
if (currentDisplayIndex < 0) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const currentPage = displayPages.value[currentDisplayIndex];
|
|
673
|
+
const logic = pageLogicByKey.value[currentPage.page_key];
|
|
674
|
+
if (logic !== void 0) {
|
|
675
|
+
for (const rule of logic.rules) {
|
|
676
|
+
if (!evaluatePageLogicRule(rule, currentPage, (field) => formState.value[field.name])) {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const gotoTarget = resolvePageLogicJumpTarget(rule, currentDisplayIndex, displayPages.value.length);
|
|
680
|
+
if (gotoTarget !== null) {
|
|
681
|
+
const targetPage = displayPages.value[gotoTarget];
|
|
682
|
+
if (targetPage !== void 0 && isPageVisible(targetPage)) {
|
|
683
|
+
return targetPage.page_key;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
for (let index = currentDisplayIndex + 1; index < displayPages.value.length; index += 1) {
|
|
689
|
+
const page = displayPages.value[index];
|
|
690
|
+
if (page !== void 0 && isPageVisible(page)) {
|
|
691
|
+
return page.page_key;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
549
696
|
const renderedPages = computed(() => {
|
|
550
|
-
if (
|
|
551
|
-
|
|
697
|
+
if (isPreviewMode.value && props.simulation) {
|
|
698
|
+
if (currentVisiblePage.value !== null) {
|
|
699
|
+
return [currentVisiblePage.value];
|
|
700
|
+
}
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
if (previewPage.value !== null) {
|
|
704
|
+
return [previewPage.value];
|
|
552
705
|
}
|
|
553
706
|
if (currentVisiblePage.value === null) {
|
|
554
707
|
return [];
|
|
@@ -559,14 +712,21 @@ const canGoPrev = computed(() => {
|
|
|
559
712
|
return activePageIndex.value > 0;
|
|
560
713
|
});
|
|
561
714
|
const canGoNext = computed(() => {
|
|
562
|
-
return
|
|
715
|
+
return resolveNextPageKey() !== null;
|
|
563
716
|
});
|
|
564
717
|
const isLastVisiblePage = computed(() => {
|
|
565
|
-
return
|
|
718
|
+
return resolveNextPageKey() === null;
|
|
566
719
|
});
|
|
567
720
|
const shouldShowErrorAlert = computed(() => {
|
|
568
721
|
return props.showAlertOnError && rendererErrors.value.length > 0;
|
|
569
722
|
});
|
|
723
|
+
function filterErrorsByFieldNames(errors, fieldNames) {
|
|
724
|
+
if (fieldNames.length === 0) {
|
|
725
|
+
return errors;
|
|
726
|
+
}
|
|
727
|
+
const fieldNameSet = new Set(fieldNames);
|
|
728
|
+
return errors.filter((error) => typeof error.name === "string" && fieldNameSet.has(error.name));
|
|
729
|
+
}
|
|
570
730
|
const resolvedSubmitLabel = computed(() => {
|
|
571
731
|
if (typeof props.submitLabel === "string" && props.submitLabel.trim() !== "") {
|
|
572
732
|
return props.submitLabel;
|
|
@@ -595,6 +755,9 @@ function isFieldVisible(field) {
|
|
|
595
755
|
return runtimeField.visible;
|
|
596
756
|
}
|
|
597
757
|
function isFieldRequired(field) {
|
|
758
|
+
if (field.type === "address") {
|
|
759
|
+
return addressFieldDefinitions(field).some((addressField) => addressField.visible && addressField.required);
|
|
760
|
+
}
|
|
598
761
|
const runtimeField = runtimeConditions.value.fields[field.field_key];
|
|
599
762
|
if (runtimeField === void 0) {
|
|
600
763
|
return field.required === true;
|
|
@@ -618,23 +781,43 @@ function setActivePageIndex(index) {
|
|
|
618
781
|
const page = visiblePages.value[safeIndex];
|
|
619
782
|
activePageKey.value = page.page_key;
|
|
620
783
|
}
|
|
621
|
-
function
|
|
622
|
-
if (typeof value !== "number") {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
setActivePageIndex(value - 1);
|
|
626
|
-
}
|
|
627
|
-
function goToPrevPage() {
|
|
784
|
+
async function goToPrevPage() {
|
|
628
785
|
if (!canGoPrev.value) {
|
|
629
786
|
return;
|
|
630
787
|
}
|
|
631
|
-
|
|
788
|
+
isPageTransitioning.value = true;
|
|
789
|
+
try {
|
|
790
|
+
setActivePageIndex(activePageIndex.value - 1);
|
|
791
|
+
} finally {
|
|
792
|
+
await Promise.resolve();
|
|
793
|
+
isPageTransitioning.value = false;
|
|
794
|
+
}
|
|
632
795
|
}
|
|
633
|
-
function goToNextPage() {
|
|
634
|
-
|
|
635
|
-
|
|
796
|
+
async function goToNextPage() {
|
|
797
|
+
isPageTransitioning.value = true;
|
|
798
|
+
try {
|
|
799
|
+
const currentPage = currentVisiblePage.value;
|
|
800
|
+
if (currentPage !== null) {
|
|
801
|
+
const fieldNames = currentPage.fields.map((field) => field.name).filter((fieldName) => fieldName.trim() !== "");
|
|
802
|
+
if (fieldNames.length > 0) {
|
|
803
|
+
const isValid = await validateForm({
|
|
804
|
+
name: fieldNames,
|
|
805
|
+
nested: true
|
|
806
|
+
});
|
|
807
|
+
if (!isValid) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const nextPageKey = resolveNextPageKey();
|
|
813
|
+
if (nextPageKey === null) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
setActivePage(nextPageKey);
|
|
817
|
+
} finally {
|
|
818
|
+
await Promise.resolve();
|
|
819
|
+
isPageTransitioning.value = false;
|
|
636
820
|
}
|
|
637
|
-
setActivePageIndex(activePageIndex.value + 1);
|
|
638
821
|
}
|
|
639
822
|
function resolvePageIndexByFieldName(fieldName) {
|
|
640
823
|
const pageKey = pageKeyByFieldName.value[fieldName];
|
|
@@ -671,7 +854,18 @@ async function validateForm(options = {}) {
|
|
|
671
854
|
return true;
|
|
672
855
|
}
|
|
673
856
|
try {
|
|
674
|
-
await formInstance.validate(options);
|
|
857
|
+
const result = await formInstance.validate(options);
|
|
858
|
+
if (result === false) {
|
|
859
|
+
const requestedFieldNames = Array.isArray(options.name) ? options.name : typeof options.name === "string" ? [options.name] : [];
|
|
860
|
+
const errors = filterErrorsByFieldNames(formInstance.getErrors(), requestedFieldNames);
|
|
861
|
+
rendererErrors.value = errors.map((error) => ({
|
|
862
|
+
id: error.id,
|
|
863
|
+
name: error.name,
|
|
864
|
+
message: error.message
|
|
865
|
+
}));
|
|
866
|
+
navigateToFirstErrorPage(rendererErrors.value);
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
675
869
|
rendererErrors.value = [];
|
|
676
870
|
return true;
|
|
677
871
|
} catch {
|
|
@@ -715,7 +909,7 @@ const shouldValidateFieldOnBlur = computed(() => {
|
|
|
715
909
|
return usesExternalModel.value;
|
|
716
910
|
});
|
|
717
911
|
function onFieldBlur(fieldName) {
|
|
718
|
-
if (!shouldValidateFieldOnBlur.value) {
|
|
912
|
+
if (!shouldValidateFieldOnBlur.value || isPageTransitioning.value) {
|
|
719
913
|
return;
|
|
720
914
|
}
|
|
721
915
|
validateField(fieldName).catch(() => {
|
|
@@ -727,81 +921,33 @@ defineExpose({
|
|
|
727
921
|
clearErrors,
|
|
728
922
|
getErrors
|
|
729
923
|
});
|
|
730
|
-
function hasDateMethod(value) {
|
|
731
|
-
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
return typeof value.toDate === "function";
|
|
735
|
-
}
|
|
736
924
|
function hasToStringMethod(value) {
|
|
737
925
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
738
926
|
return false;
|
|
739
927
|
}
|
|
740
928
|
return typeof value.toString === "function";
|
|
741
929
|
}
|
|
742
|
-
function isRangeInput(value) {
|
|
743
|
-
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
return "start" in value && "end" in value;
|
|
747
|
-
}
|
|
748
930
|
function isFileValue(value) {
|
|
749
931
|
if (typeof File === "undefined") {
|
|
750
932
|
return false;
|
|
751
933
|
}
|
|
752
934
|
return value instanceof File;
|
|
753
935
|
}
|
|
754
|
-
function
|
|
755
|
-
if (value.endsWith("Z")) {
|
|
756
|
-
return value.slice(0, -1);
|
|
757
|
-
}
|
|
758
|
-
const match = value.match(/^(.*)([+-]\d{2}:\d{2})$/);
|
|
759
|
-
if (match === null) {
|
|
760
|
-
return value;
|
|
761
|
-
}
|
|
762
|
-
return match[1];
|
|
763
|
-
}
|
|
764
|
-
function formatTwoDigits(value) {
|
|
765
|
-
return String(value).padStart(2, "0");
|
|
766
|
-
}
|
|
767
|
-
function serializeDateWithOffset(date) {
|
|
768
|
-
const year = date.getFullYear();
|
|
769
|
-
const month = formatTwoDigits(date.getMonth() + 1);
|
|
770
|
-
const day = formatTwoDigits(date.getDate());
|
|
771
|
-
const hours = formatTwoDigits(date.getHours());
|
|
772
|
-
const minutes = formatTwoDigits(date.getMinutes());
|
|
773
|
-
const seconds = formatTwoDigits(date.getSeconds());
|
|
774
|
-
const offsetMinutes = -date.getTimezoneOffset();
|
|
775
|
-
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
776
|
-
const absoluteOffset = Math.abs(offsetMinutes);
|
|
777
|
-
const offsetHours = formatTwoDigits(Math.floor(absoluteOffset / 60));
|
|
778
|
-
const offsetRemainder = formatTwoDigits(absoluteOffset % 60);
|
|
779
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${sign}${offsetHours}:${offsetRemainder}`;
|
|
780
|
-
}
|
|
781
|
-
function serializeDateAsUtc(date) {
|
|
782
|
-
return date.toISOString().replace(".000Z", "Z");
|
|
783
|
-
}
|
|
784
|
-
function parseSingleDateValue(type, value) {
|
|
936
|
+
function parseSingleDateValue(mode, value) {
|
|
785
937
|
try {
|
|
786
|
-
if (
|
|
938
|
+
if (mode === "date") {
|
|
787
939
|
return parseDate(value);
|
|
788
940
|
}
|
|
789
|
-
if (
|
|
941
|
+
if (mode === "time") {
|
|
790
942
|
return parseTime(value);
|
|
791
943
|
}
|
|
792
|
-
if (type === "datetime") {
|
|
793
|
-
if (value.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(value)) {
|
|
794
|
-
return parseAbsoluteToLocal(value);
|
|
795
|
-
}
|
|
796
|
-
return parseDateTime(stripDatetimeOffset(value));
|
|
797
|
-
}
|
|
798
944
|
} catch {
|
|
799
945
|
return null;
|
|
800
946
|
}
|
|
801
947
|
return null;
|
|
802
948
|
}
|
|
803
|
-
function serializeSingleDateValue(
|
|
804
|
-
if (
|
|
949
|
+
function serializeSingleDateValue(mode, value) {
|
|
950
|
+
if (mode === "date" || mode === "time") {
|
|
805
951
|
if (hasToStringMethod(value)) {
|
|
806
952
|
return value.toString();
|
|
807
953
|
}
|
|
@@ -810,60 +956,32 @@ function serializeSingleDateValue(type, value, mode) {
|
|
|
810
956
|
}
|
|
811
957
|
return null;
|
|
812
958
|
}
|
|
813
|
-
if (type === "datetime") {
|
|
814
|
-
if (hasDateMethod(value)) {
|
|
815
|
-
const date = value.toDate(getLocalTimeZone());
|
|
816
|
-
return mode === "utc" ? serializeDateAsUtc(date) : serializeDateWithOffset(date);
|
|
817
|
-
}
|
|
818
|
-
if (typeof value === "string") {
|
|
819
|
-
return value;
|
|
820
|
-
}
|
|
821
|
-
return null;
|
|
822
|
-
}
|
|
823
959
|
return null;
|
|
824
960
|
}
|
|
825
|
-
function
|
|
826
|
-
if (
|
|
827
|
-
return
|
|
961
|
+
function choiceDisplayValue(field) {
|
|
962
|
+
if (field.display === "list" || field.display === "menu") {
|
|
963
|
+
return field.display;
|
|
828
964
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const endValue = rangeValue.end;
|
|
832
|
-
const parsedStart = typeof startValue === "string" ? parseSingleDateValue(type === "date_range" ? "date" : "datetime", startValue) : null;
|
|
833
|
-
const parsedEnd = typeof endValue === "string" ? parseSingleDateValue(type === "date_range" ? "date" : "datetime", endValue) : null;
|
|
834
|
-
return {
|
|
835
|
-
start: parsedStart,
|
|
836
|
-
end: parsedEnd
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
function serializeRangeValue(type, value) {
|
|
840
|
-
if (!isRangeInput(value)) {
|
|
841
|
-
return {
|
|
842
|
-
start: null,
|
|
843
|
-
end: null
|
|
844
|
-
};
|
|
965
|
+
if (field.type === "radio" || field.type === "checkbox_group") {
|
|
966
|
+
return "list";
|
|
845
967
|
}
|
|
846
|
-
|
|
847
|
-
const endValue = serializeSingleDateValue(type === "date_range" ? "date" : "datetime", value.end, props.datetimeMode);
|
|
848
|
-
return {
|
|
849
|
-
start: startValue,
|
|
850
|
-
end: endValue
|
|
851
|
-
};
|
|
968
|
+
return "menu";
|
|
852
969
|
}
|
|
853
970
|
function normalizeSelectOptions(options) {
|
|
854
971
|
if (options === void 0) {
|
|
855
972
|
return [];
|
|
856
973
|
}
|
|
857
974
|
const items = [];
|
|
858
|
-
for (const option of options) {
|
|
975
|
+
for (const [index, option] of options.entries()) {
|
|
859
976
|
if (typeof option === "string" || typeof option === "number" || typeof option === "boolean" || option === null) {
|
|
977
|
+
const primitiveLabel = option === null ? "" : String(option);
|
|
860
978
|
items.push({
|
|
861
|
-
label:
|
|
979
|
+
label: primitiveLabel.trim() === "" ? `Option ${index + 1}` : primitiveLabel,
|
|
862
980
|
value: option
|
|
863
981
|
});
|
|
864
982
|
continue;
|
|
865
983
|
}
|
|
866
|
-
const label = typeof option.label === "string"
|
|
984
|
+
const label = typeof option.label === "string" && option.label.trim() !== "" ? option.label : `Option ${index + 1}`;
|
|
867
985
|
items.push({
|
|
868
986
|
label,
|
|
869
987
|
value: option.value,
|
|
@@ -902,6 +1020,15 @@ function getResolvedFormFieldUi(field) {
|
|
|
902
1020
|
function getFieldValue(field) {
|
|
903
1021
|
return formState.value[field.name];
|
|
904
1022
|
}
|
|
1023
|
+
function addressFieldDefinitions(field) {
|
|
1024
|
+
if (field.type !== "address") {
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
if (!Array.isArray(field.address_fields) || field.address_fields.length === 0) {
|
|
1028
|
+
return createDefaultAddressFields(locale.value);
|
|
1029
|
+
}
|
|
1030
|
+
return field.address_fields;
|
|
1031
|
+
}
|
|
905
1032
|
function setFieldValue(fieldName, value) {
|
|
906
1033
|
formState.value = {
|
|
907
1034
|
...formState.value,
|
|
@@ -910,21 +1037,29 @@ function setFieldValue(fieldName, value) {
|
|
|
910
1037
|
}
|
|
911
1038
|
function getComponentModelValue(field) {
|
|
912
1039
|
const value = getFieldValue(field);
|
|
913
|
-
|
|
1040
|
+
const temporalMode = resolveTemporalMode(field);
|
|
1041
|
+
if (field.type === "temporal" || field.type === "date" || field.type === "time") {
|
|
914
1042
|
if (typeof value === "string") {
|
|
915
|
-
return parseSingleDateValue(
|
|
1043
|
+
return parseSingleDateValue(temporalMode, value);
|
|
916
1044
|
}
|
|
917
1045
|
return null;
|
|
918
1046
|
}
|
|
919
|
-
if (field.type === "
|
|
920
|
-
return parseRangeValue(field.type, value);
|
|
921
|
-
}
|
|
922
|
-
if (field.type === "checkbox" || field.type === "switch") {
|
|
1047
|
+
if (field.type === "checkbox" || field.type === "consent" || field.type === "switch") {
|
|
923
1048
|
return typeof value === "boolean" ? value : false;
|
|
924
1049
|
}
|
|
925
1050
|
if (field.type === "checkbox_group") {
|
|
926
1051
|
return Array.isArray(value) ? value : [];
|
|
927
1052
|
}
|
|
1053
|
+
if (field.type === "address") {
|
|
1054
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1055
|
+
return value;
|
|
1056
|
+
}
|
|
1057
|
+
const nextValue = {};
|
|
1058
|
+
for (const addressField of addressFieldDefinitions(field)) {
|
|
1059
|
+
nextValue[addressField.key] = null;
|
|
1060
|
+
}
|
|
1061
|
+
return nextValue;
|
|
1062
|
+
}
|
|
928
1063
|
if (field.type === "file") {
|
|
929
1064
|
if (field.multiple === true) {
|
|
930
1065
|
if (Array.isArray(value)) {
|
|
@@ -965,14 +1100,15 @@ function getComponentProps(field, page) {
|
|
|
965
1100
|
componentProps.max = field.max ?? void 0;
|
|
966
1101
|
componentProps.step = field.step ?? void 0;
|
|
967
1102
|
}
|
|
1103
|
+
if (field.type === "consent") {
|
|
1104
|
+
componentProps.label = field.consent_label ?? field.label ?? "";
|
|
1105
|
+
componentProps.required = false;
|
|
1106
|
+
}
|
|
968
1107
|
if (field.type === "select" || field.type === "select_menu" || field.type === "radio" || field.type === "checkbox_group") {
|
|
969
1108
|
componentProps.items = normalizeSelectOptions(field.options);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
if (field.type === "datetime" || field.type === "datetime_range") {
|
|
975
|
-
componentProps.granularity = "second";
|
|
1109
|
+
if (field.type === "checkbox_group" && choiceDisplayValue(field) === "menu") {
|
|
1110
|
+
componentProps.multiple = true;
|
|
1111
|
+
}
|
|
976
1112
|
}
|
|
977
1113
|
if (field.type === "file") {
|
|
978
1114
|
componentProps.multiple = field.multiple === true;
|
|
@@ -987,12 +1123,9 @@ function getComponentProps(field, page) {
|
|
|
987
1123
|
return componentProps;
|
|
988
1124
|
}
|
|
989
1125
|
function onFieldUpdate(field, value) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
if (field.type === "date_range" || field.type === "datetime_range") {
|
|
995
|
-
setFieldValue(field.name, serializeRangeValue(field.type, value));
|
|
1126
|
+
const temporalMode = resolveTemporalMode(field);
|
|
1127
|
+
if (field.type === "temporal" || field.type === "date" || field.type === "time") {
|
|
1128
|
+
setFieldValue(field.name, serializeSingleDateValue(temporalMode, value));
|
|
996
1129
|
return;
|
|
997
1130
|
}
|
|
998
1131
|
setFieldValue(field.name, value);
|
|
@@ -1017,7 +1150,8 @@ async function onSubmit() {
|
|
|
1017
1150
|
const response = await internalSubmit.submit({
|
|
1018
1151
|
test: props.simulation,
|
|
1019
1152
|
version: props.formVersion,
|
|
1020
|
-
mode: props.uploadMode
|
|
1153
|
+
mode: props.uploadMode,
|
|
1154
|
+
meta: requiresSubmissionCode.value ? { submission_code: submissionCode.value } : submissionCode.value.trim() !== "" ? { submission_code: submissionCode.value } : void 0
|
|
1021
1155
|
});
|
|
1022
1156
|
submittedResponse.value = response;
|
|
1023
1157
|
if (props.clearAfterSubmit) {
|
|
@@ -1058,20 +1192,10 @@ async function onSubmit() {
|
|
|
1058
1192
|
@error="onFormError"
|
|
1059
1193
|
>
|
|
1060
1194
|
<div class="space-y-6">
|
|
1061
|
-
<UStepper
|
|
1062
|
-
v-if="shouldShowProgress && progressVariant === 'stepper'"
|
|
1063
|
-
class="w-full"
|
|
1064
|
-
:items="stepperItems"
|
|
1065
|
-
:model-value="activePageIndex + 1"
|
|
1066
|
-
:linear="false"
|
|
1067
|
-
@update:model-value="onStepperModelUpdate"
|
|
1068
|
-
/>
|
|
1069
|
-
|
|
1070
1195
|
<UProgress
|
|
1071
|
-
v-if="shouldShowProgress
|
|
1072
|
-
:model-value="
|
|
1073
|
-
:max="
|
|
1074
|
-
status
|
|
1196
|
+
v-if="shouldShowProgress"
|
|
1197
|
+
:model-value="progressValue"
|
|
1198
|
+
:max="100"
|
|
1075
1199
|
/>
|
|
1076
1200
|
|
|
1077
1201
|
<UAlert
|
|
@@ -1123,132 +1247,54 @@ async function onSubmit() {
|
|
|
1123
1247
|
:title="t('renderer.alert.submitted')"
|
|
1124
1248
|
/>
|
|
1125
1249
|
|
|
1126
|
-
<
|
|
1250
|
+
<UAlert
|
|
1251
|
+
v-if="availabilityAlert !== null"
|
|
1252
|
+
color="warning"
|
|
1253
|
+
variant="soft"
|
|
1254
|
+
:title="availabilityAlert"
|
|
1255
|
+
/>
|
|
1256
|
+
|
|
1257
|
+
<div
|
|
1258
|
+
v-if="requiresSubmissionCode"
|
|
1259
|
+
class="space-y-2 rounded-xl border border-muted bg-elevated/40 p-4"
|
|
1260
|
+
>
|
|
1261
|
+
<p class="text-sm font-medium text-default">
|
|
1262
|
+
{{ t("renderer.submissionCodeLabel") }}
|
|
1263
|
+
</p>
|
|
1264
|
+
<UInput
|
|
1265
|
+
v-model="submissionCode"
|
|
1266
|
+
type="password"
|
|
1267
|
+
autocomplete="one-time-code"
|
|
1268
|
+
:placeholder="t('renderer.submissionCodePlaceholder')"
|
|
1269
|
+
/>
|
|
1270
|
+
</div>
|
|
1271
|
+
|
|
1272
|
+
<FormForgeRendererPage
|
|
1127
1273
|
v-for="page in renderedPages"
|
|
1128
1274
|
:key="page.page_key"
|
|
1275
|
+
:page="page"
|
|
1129
1276
|
class="space-y-4"
|
|
1130
1277
|
@focusin="setActivePage(page.page_key)"
|
|
1131
1278
|
@pointerdown="setActivePage(page.page_key)"
|
|
1132
1279
|
>
|
|
1133
|
-
<
|
|
1134
|
-
v-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
</p>
|
|
1149
|
-
</div>
|
|
1150
|
-
|
|
1151
|
-
<div class="space-y-4">
|
|
1152
|
-
<UFormField
|
|
1153
|
-
v-for="field in page.fields"
|
|
1154
|
-
v-show="isFieldVisible(field)"
|
|
1155
|
-
:key="field.field_key"
|
|
1156
|
-
:name="field.name"
|
|
1157
|
-
:label="field.label"
|
|
1158
|
-
:help="field.help_text"
|
|
1159
|
-
:required="isFieldRequired(field)"
|
|
1160
|
-
:ui="getResolvedFormFieldUi(field)"
|
|
1161
|
-
@focusout="() => onFieldBlur(field.name)"
|
|
1162
|
-
>
|
|
1163
|
-
<UInput
|
|
1164
|
-
v-if="field.type === 'text' || field.type === 'email'"
|
|
1165
|
-
:model-value="getLooseModelValue(field)"
|
|
1166
|
-
v-bind="getComponentProps(field, page)"
|
|
1167
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1168
|
-
/>
|
|
1169
|
-
|
|
1170
|
-
<UTextarea
|
|
1171
|
-
v-else-if="field.type === 'textarea'"
|
|
1172
|
-
:model-value="getLooseModelValue(field)"
|
|
1173
|
-
v-bind="getComponentProps(field, page)"
|
|
1174
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1175
|
-
/>
|
|
1176
|
-
|
|
1177
|
-
<UInputNumber
|
|
1178
|
-
v-else-if="field.type === 'number'"
|
|
1179
|
-
:model-value="getLooseModelValue(field)"
|
|
1180
|
-
v-bind="getComponentProps(field, page)"
|
|
1181
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1182
|
-
/>
|
|
1183
|
-
|
|
1184
|
-
<USelect
|
|
1185
|
-
v-else-if="field.type === 'select'"
|
|
1186
|
-
:model-value="getLooseModelValue(field)"
|
|
1187
|
-
v-bind="getComponentProps(field, page)"
|
|
1188
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1189
|
-
/>
|
|
1190
|
-
|
|
1191
|
-
<USelectMenu
|
|
1192
|
-
v-else-if="field.type === 'select_menu'"
|
|
1193
|
-
:model-value="getLooseModelValue(field)"
|
|
1194
|
-
v-bind="getComponentProps(field, page)"
|
|
1195
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1196
|
-
/>
|
|
1197
|
-
|
|
1198
|
-
<URadioGroup
|
|
1199
|
-
v-else-if="field.type === 'radio'"
|
|
1200
|
-
:model-value="getLooseModelValue(field)"
|
|
1201
|
-
v-bind="getComponentProps(field, page)"
|
|
1202
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1203
|
-
/>
|
|
1204
|
-
|
|
1205
|
-
<UCheckbox
|
|
1206
|
-
v-else-if="field.type === 'checkbox'"
|
|
1207
|
-
:model-value="getLooseModelValue(field)"
|
|
1208
|
-
v-bind="getComponentProps(field, page)"
|
|
1209
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1210
|
-
/>
|
|
1211
|
-
|
|
1212
|
-
<UCheckboxGroup
|
|
1213
|
-
v-else-if="field.type === 'checkbox_group'"
|
|
1214
|
-
:model-value="getLooseModelValue(field)"
|
|
1215
|
-
v-bind="getComponentProps(field, page)"
|
|
1216
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1217
|
-
/>
|
|
1218
|
-
|
|
1219
|
-
<USwitch
|
|
1220
|
-
v-else-if="field.type === 'switch'"
|
|
1221
|
-
:model-value="getLooseModelValue(field)"
|
|
1222
|
-
v-bind="getComponentProps(field, page)"
|
|
1223
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1224
|
-
/>
|
|
1225
|
-
|
|
1226
|
-
<UInputDate
|
|
1227
|
-
v-else-if="field.type === 'date' || field.type === 'datetime' || field.type === 'date_range' || field.type === 'datetime_range'"
|
|
1228
|
-
:model-value="getLooseModelValue(field)"
|
|
1229
|
-
v-bind="getComponentProps(field, page)"
|
|
1230
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1231
|
-
/>
|
|
1232
|
-
|
|
1233
|
-
<UInputTime
|
|
1234
|
-
v-else-if="field.type === 'time'"
|
|
1235
|
-
:model-value="getLooseModelValue(field)"
|
|
1236
|
-
v-bind="getComponentProps(field, page)"
|
|
1237
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1238
|
-
/>
|
|
1239
|
-
|
|
1240
|
-
<UFileUpload
|
|
1241
|
-
v-else
|
|
1242
|
-
:model-value="getLooseModelValue(field)"
|
|
1243
|
-
v-bind="getComponentProps(field, page)"
|
|
1244
|
-
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1245
|
-
/>
|
|
1246
|
-
</UFormField>
|
|
1247
|
-
</div>
|
|
1248
|
-
</section>
|
|
1280
|
+
<FormForgeRendererField
|
|
1281
|
+
v-for="field in page.fields"
|
|
1282
|
+
v-show="isFieldVisible(field)"
|
|
1283
|
+
:key="field.field_key"
|
|
1284
|
+
:field="field"
|
|
1285
|
+
:model-value="getLooseModelValue(field)"
|
|
1286
|
+
:component-props="getComponentProps(field, page)"
|
|
1287
|
+
:field-ui="getResolvedFormFieldUi(field)"
|
|
1288
|
+
:address-fields="addressFieldDefinitions(field)"
|
|
1289
|
+
:required="isFieldRequired(field)"
|
|
1290
|
+
:disabled="isFieldDisabled(field, page)"
|
|
1291
|
+
@update:model-value="(nextValue) => onFieldModelUpdate(field, nextValue)"
|
|
1292
|
+
@blur="() => onFieldBlur(field.name)"
|
|
1293
|
+
/>
|
|
1294
|
+
</FormForgeRendererPage>
|
|
1249
1295
|
|
|
1250
1296
|
<div
|
|
1251
|
-
v-if="
|
|
1297
|
+
v-if="shouldShowNavigation"
|
|
1252
1298
|
class="flex items-center justify-between gap-2"
|
|
1253
1299
|
>
|
|
1254
1300
|
<UButton
|
|
@@ -1271,23 +1317,23 @@ async function onSubmit() {
|
|
|
1271
1317
|
</UButton>
|
|
1272
1318
|
|
|
1273
1319
|
<UButton
|
|
1274
|
-
v-else-if="
|
|
1320
|
+
v-else-if="showSubmit"
|
|
1275
1321
|
type="submit"
|
|
1276
1322
|
:loading="internalSubmit.submitting.value"
|
|
1277
|
-
:disabled="internalForm.loading.value || getResolvedSchema() === null"
|
|
1323
|
+
:disabled="internalForm.loading.value || getResolvedSchema() === null || availabilityAlert !== null || requiresSubmissionCode && submissionCode.trim() === ''"
|
|
1278
1324
|
>
|
|
1279
1325
|
{{ resolvedSubmitLabel }}
|
|
1280
1326
|
</UButton>
|
|
1281
1327
|
</div>
|
|
1282
1328
|
|
|
1283
1329
|
<div
|
|
1284
|
-
v-else-if="
|
|
1330
|
+
v-else-if="showSubmit"
|
|
1285
1331
|
class="flex justify-end"
|
|
1286
1332
|
>
|
|
1287
1333
|
<UButton
|
|
1288
1334
|
type="submit"
|
|
1289
1335
|
:loading="internalSubmit.submitting.value"
|
|
1290
|
-
:disabled="internalForm.loading.value || getResolvedSchema() === null"
|
|
1336
|
+
:disabled="internalForm.loading.value || getResolvedSchema() === null || availabilityAlert !== null"
|
|
1291
1337
|
>
|
|
1292
1338
|
{{ resolvedSubmitLabel }}
|
|
1293
1339
|
</UButton>
|
|
@@ -1295,3 +1341,7 @@ async function onSubmit() {
|
|
|
1295
1341
|
</div>
|
|
1296
1342
|
</UForm>
|
|
1297
1343
|
</template>
|
|
1344
|
+
|
|
1345
|
+
<style scoped>
|
|
1346
|
+
.formforge-rich-text :deep(p){margin:.25rem 0}.formforge-rich-text :deep(p:first-child){margin-top:0}.formforge-rich-text :deep(p:last-child){margin-bottom:0}.formforge-rich-text :deep(ol),.formforge-rich-text :deep(ul){margin:.25rem 0;padding-left:1.25rem}.formforge-rich-text :deep(a){text-decoration:underline}
|
|
1347
|
+
</style>
|