@evanschleret/formforgeclient 1.0.0 → 1.1.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/dist/module.json +1 -1
- package/dist/runtime/api/client.js +11 -1
- package/dist/runtime/api/management.js +15 -2
- package/dist/runtime/composables/useFormForgeBuilder.d.ts +5 -1
- package/dist/runtime/composables/useFormForgeBuilder.js +12 -5
- package/dist/runtime/composables/useFormForgeClient.js +21 -3
- package/dist/runtime/composables/useFormForgeI18n.d.ts +6 -0
- package/dist/runtime/composables/useFormForgeI18n.js +6 -0
- package/dist/runtime/plugin.js +19 -3
- package/dist/runtime/renderers/default/FormForgeBuilder.d.vue.ts +8 -0
- package/dist/runtime/renderers/default/FormForgeBuilder.vue +126 -20
- package/dist/runtime/renderers/default/FormForgeBuilder.vue.d.ts +8 -0
- package/dist/runtime/renderers/default/FormForgeRenderer.d.vue.ts +26 -1
- package/dist/runtime/renderers/default/FormForgeRenderer.vue +91 -20
- package/dist/runtime/renderers/default/FormForgeRenderer.vue.d.ts +26 -1
- package/dist/runtime/types/management.d.ts +4 -0
- package/package.json +5 -1
package/dist/module.json
CHANGED
|
@@ -24,6 +24,15 @@ class FormForgeClientImpl {
|
|
|
24
24
|
}
|
|
25
25
|
return input;
|
|
26
26
|
}
|
|
27
|
+
resolveBaseURLParams(input) {
|
|
28
|
+
if (input === void 0) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
if (typeof input === "function") {
|
|
32
|
+
return input();
|
|
33
|
+
}
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
27
36
|
resolveNamedScope(name) {
|
|
28
37
|
const scopedRoutes = this.config.scopedRoutes ?? {};
|
|
29
38
|
const routeDefinition = scopedRoutes[name];
|
|
@@ -31,9 +40,10 @@ class FormForgeClientImpl {
|
|
|
31
40
|
throw new Error(`Unknown FormForge scope "${name}"`);
|
|
32
41
|
}
|
|
33
42
|
const sourceParams = this.resolveScopeParams(this.config.scopeParams);
|
|
43
|
+
const baseURLParams = this.resolveBaseURLParams(this.config.baseURLParams);
|
|
34
44
|
const params = {};
|
|
35
45
|
for (const [scopeParam, sourceParam] of Object.entries(routeDefinition.paramsFromRoute)) {
|
|
36
|
-
const value = sourceParams[sourceParam];
|
|
46
|
+
const value = sourceParams[sourceParam] ?? baseURLParams[sourceParam];
|
|
37
47
|
if (value === void 0 || value === "") {
|
|
38
48
|
throw new Error(`Missing scope param source "${sourceParam}" for named scope "${name}"`);
|
|
39
49
|
}
|
|
@@ -58,12 +58,25 @@ function withMutationHeaders(options = {}) {
|
|
|
58
58
|
function toJsonObject(input) {
|
|
59
59
|
return JSON.parse(JSON.stringify(input));
|
|
60
60
|
}
|
|
61
|
+
function shouldAutoPublish(input) {
|
|
62
|
+
return input.auto_publish === true || input.autoPublish === true;
|
|
63
|
+
}
|
|
64
|
+
function toManagementMutationPayload(input) {
|
|
65
|
+
const payload = toJsonObject(input);
|
|
66
|
+
if ("autoPublish" in payload) {
|
|
67
|
+
delete payload.autoPublish;
|
|
68
|
+
}
|
|
69
|
+
if (shouldAutoPublish(input)) {
|
|
70
|
+
payload.auto_publish = true;
|
|
71
|
+
}
|
|
72
|
+
return payload;
|
|
73
|
+
}
|
|
61
74
|
export async function createFormForgeForm(http, input, options = {}) {
|
|
62
75
|
const response = await http({
|
|
63
76
|
path: resolveEndpointPath(options.endpoint, "/forms", {}, options.scope),
|
|
64
77
|
method: "POST",
|
|
65
78
|
headers: withMutationHeaders(options),
|
|
66
|
-
json:
|
|
79
|
+
json: toManagementMutationPayload(input)
|
|
67
80
|
});
|
|
68
81
|
return normalizeManagementForm(pickFormForgeDataEnvelope(response.data)) ?? {};
|
|
69
82
|
}
|
|
@@ -86,7 +99,7 @@ export async function patchFormForgeForm(http, key, input, options = {}) {
|
|
|
86
99
|
}, options.scope),
|
|
87
100
|
method: "PATCH",
|
|
88
101
|
headers: withMutationHeaders(options),
|
|
89
|
-
json:
|
|
102
|
+
json: toManagementMutationPayload(input)
|
|
90
103
|
});
|
|
91
104
|
return normalizeManagementForm(pickFormForgeDataEnvelope(response.data)) ?? {};
|
|
92
105
|
}
|
|
@@ -19,6 +19,10 @@ export interface UseFormForgeBuilderOptions {
|
|
|
19
19
|
client?: FormForgeClient;
|
|
20
20
|
clientConfig?: FormForgeClientConfig;
|
|
21
21
|
}
|
|
22
|
+
export interface FormForgeBuilderSaveOptions {
|
|
23
|
+
idempotencyKey?: string;
|
|
24
|
+
autoPublish?: boolean;
|
|
25
|
+
}
|
|
22
26
|
export declare const FORM_FORGE_BUILDER_FIELD_TYPES: FormForgeFieldType[];
|
|
23
27
|
export declare const FORM_FORGE_BUILDER_CONDITION_TARGET_TYPES: FormForgeConditionTargetType[];
|
|
24
28
|
export declare const FORM_FORGE_BUILDER_CONDITION_ACTIONS: FormForgeConditionAction[];
|
|
@@ -48,7 +52,7 @@ export declare function useFormForgeBuilder(options?: UseFormForgeBuilderOptions
|
|
|
48
52
|
addConditionClause: (conditionKey: string) => void;
|
|
49
53
|
removeConditionClause: (conditionKey: string, index: number) => void;
|
|
50
54
|
normalizeFieldLocations: () => void;
|
|
51
|
-
save: (
|
|
55
|
+
save: (saveOptions?: FormForgeBuilderSaveOptions | string) => Promise<void>;
|
|
52
56
|
publish: (idempotencyKey?: string) => Promise<void>;
|
|
53
57
|
unpublish: (idempotencyKey?: string) => Promise<void>;
|
|
54
58
|
clearAutosave: () => void;
|
|
@@ -234,7 +234,7 @@ export function useFormForgeBuilder(options = {}) {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
-
function toManagementInput() {
|
|
237
|
+
function toManagementInput(autoPublish = false) {
|
|
238
238
|
normalizeFieldLocations();
|
|
239
239
|
const pages = draft.value.pages;
|
|
240
240
|
const fields = [];
|
|
@@ -251,16 +251,23 @@ export function useFormForgeBuilder(options = {}) {
|
|
|
251
251
|
conditions: draft.value.conditions,
|
|
252
252
|
drafts: draft.value.drafts
|
|
253
253
|
};
|
|
254
|
+
if (autoPublish) {
|
|
255
|
+
input.auto_publish = true;
|
|
256
|
+
}
|
|
254
257
|
return input;
|
|
255
258
|
}
|
|
256
|
-
async function save(
|
|
259
|
+
async function save(saveOptions = {}) {
|
|
260
|
+
const resolvedSaveOptions = typeof saveOptions === "string" ? { idempotencyKey: saveOptions, autoPublish: false } : {
|
|
261
|
+
idempotencyKey: saveOptions.idempotencyKey,
|
|
262
|
+
autoPublish: saveOptions.autoPublish === true
|
|
263
|
+
};
|
|
257
264
|
if (draft.value.title.trim() === "") {
|
|
258
265
|
throw new Error("Title is required to save");
|
|
259
266
|
}
|
|
260
267
|
saving.value = true;
|
|
261
268
|
error.value = null;
|
|
262
269
|
try {
|
|
263
|
-
const input = toManagementInput();
|
|
270
|
+
const input = toManagementInput(resolvedSaveOptions.autoPublish);
|
|
264
271
|
const mutationIdentifier = resolveMutationIdentifier(draft.value);
|
|
265
272
|
if (mutationIdentifier === null) {
|
|
266
273
|
const hasExistingSlug = typeof draft.value.key === "string" && draft.value.key !== "";
|
|
@@ -268,7 +275,7 @@ export function useFormForgeBuilder(options = {}) {
|
|
|
268
275
|
throw new Error("Form uuid is required for update");
|
|
269
276
|
}
|
|
270
277
|
const created = await client.createForm(input, {
|
|
271
|
-
idempotencyKey,
|
|
278
|
+
idempotencyKey: resolvedSaveOptions.idempotencyKey,
|
|
272
279
|
endpoint: options.endpoint,
|
|
273
280
|
scope: options.scope
|
|
274
281
|
});
|
|
@@ -283,7 +290,7 @@ export function useFormForgeBuilder(options = {}) {
|
|
|
283
290
|
} else {
|
|
284
291
|
const patchInput = input;
|
|
285
292
|
const patched = await client.patchForm(mutationIdentifier, patchInput, {
|
|
286
|
-
idempotencyKey,
|
|
293
|
+
idempotencyKey: resolvedSaveOptions.idempotencyKey,
|
|
287
294
|
endpoint: options.endpoint,
|
|
288
295
|
scope: options.scope
|
|
289
296
|
});
|
|
@@ -32,7 +32,7 @@ function mergeRouteValues(primaryValues, secondaryValues) {
|
|
|
32
32
|
return merged;
|
|
33
33
|
}
|
|
34
34
|
function templateSegmentKey(value) {
|
|
35
|
-
const matched = value.match(/^\{([a-zA-Z0-9_]+)
|
|
35
|
+
const matched = value.match(/^\{([a-zA-Z0-9_]+)(?::[^}]+)?\}$/);
|
|
36
36
|
return matched?.[1] ?? null;
|
|
37
37
|
}
|
|
38
38
|
function toPathSegments(path) {
|
|
@@ -104,6 +104,19 @@ function withInferredMissingValues(base, inferred) {
|
|
|
104
104
|
}
|
|
105
105
|
return resolved;
|
|
106
106
|
}
|
|
107
|
+
function inferScopeSourcesFromPath(scopedRoutes, currentPath) {
|
|
108
|
+
const inferredSources = {};
|
|
109
|
+
for (const scopedRoute of Object.values(scopedRoutes ?? {})) {
|
|
110
|
+
const inferredScopeParams = inferParamsFromPath(scopedRoute.prefix, currentPath);
|
|
111
|
+
for (const [scopeParam, sourceParam] of Object.entries(scopedRoute.paramsFromRoute)) {
|
|
112
|
+
const inferredValue = inferredScopeParams[scopeParam];
|
|
113
|
+
if (inferredValue !== void 0 && inferredValue !== "" && inferredSources[sourceParam] === void 0) {
|
|
114
|
+
inferredSources[sourceParam] = inferredValue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return inferredSources;
|
|
119
|
+
}
|
|
107
120
|
export function useFormForgeClient(config = {}) {
|
|
108
121
|
const nuxtApp = useNuxtApp();
|
|
109
122
|
const route = useRoute();
|
|
@@ -130,8 +143,13 @@ export function useFormForgeClient(config = {}) {
|
|
|
130
143
|
...appRouteParams
|
|
131
144
|
}
|
|
132
145
|
);
|
|
133
|
-
const
|
|
134
|
-
|
|
146
|
+
const resolvedPath = composableRoutePath || appRoutePath;
|
|
147
|
+
const resolvedBaseURL = config.baseURL ?? runtimePublicConfig?.baseURL;
|
|
148
|
+
const resolvedScopedRoutes = config.scopedRoutes ?? runtimePublicConfig?.scopedRoutes;
|
|
149
|
+
const inferredFromBaseURL = inferParamsFromPath(resolvedBaseURL, resolvedPath);
|
|
150
|
+
const withBaseURLValues = withInferredMissingValues(mergedRouteValues, inferredFromBaseURL);
|
|
151
|
+
const inferredFromScopes = inferScopeSourcesFromPath(resolvedScopedRoutes, resolvedPath);
|
|
152
|
+
return withInferredMissingValues(withBaseURLValues, inferredFromScopes);
|
|
135
153
|
};
|
|
136
154
|
const baseConfig = runtimePublicConfig;
|
|
137
155
|
const mergedConfig = {
|
|
@@ -84,6 +84,9 @@ declare const TRANSLATIONS: {
|
|
|
84
84
|
readonly 'builder.error.publish': "Publish failed";
|
|
85
85
|
readonly 'builder.error.unpublish': "Unpublish failed";
|
|
86
86
|
readonly 'builder.error.categoryCreate': "Category creation failed";
|
|
87
|
+
readonly 'builder.toast.saveSuccess': "Form saved";
|
|
88
|
+
readonly 'builder.toast.publishSuccess': "Form published";
|
|
89
|
+
readonly 'builder.toast.unpublishSuccess': "Form unpublished";
|
|
87
90
|
readonly 'builder.optionDefaultLabel': "Option";
|
|
88
91
|
readonly 'builder.optionsCount': "{count} options";
|
|
89
92
|
readonly 'builder.categoryModal.title': "Create category";
|
|
@@ -202,6 +205,9 @@ declare const TRANSLATIONS: {
|
|
|
202
205
|
readonly 'builder.error.publish': "Échec de la publication";
|
|
203
206
|
readonly 'builder.error.unpublish': "Échec de la dépublication";
|
|
204
207
|
readonly 'builder.error.categoryCreate': "Échec de la création de catégorie";
|
|
208
|
+
readonly 'builder.toast.saveSuccess': "Formulaire sauvegardé";
|
|
209
|
+
readonly 'builder.toast.publishSuccess': "Formulaire publié";
|
|
210
|
+
readonly 'builder.toast.unpublishSuccess': "Formulaire dépublié";
|
|
205
211
|
readonly 'builder.optionDefaultLabel': "Option";
|
|
206
212
|
readonly 'builder.optionsCount': "{count} options";
|
|
207
213
|
readonly 'builder.categoryModal.title': "Créer une catégorie";
|
|
@@ -80,6 +80,9 @@ const TRANSLATIONS = {
|
|
|
80
80
|
"builder.error.publish": "Publish failed",
|
|
81
81
|
"builder.error.unpublish": "Unpublish failed",
|
|
82
82
|
"builder.error.categoryCreate": "Category creation failed",
|
|
83
|
+
"builder.toast.saveSuccess": "Form saved",
|
|
84
|
+
"builder.toast.publishSuccess": "Form published",
|
|
85
|
+
"builder.toast.unpublishSuccess": "Form unpublished",
|
|
83
86
|
"builder.optionDefaultLabel": "Option",
|
|
84
87
|
"builder.optionsCount": "{count} options",
|
|
85
88
|
"builder.categoryModal.title": "Create category",
|
|
@@ -198,6 +201,9 @@ const TRANSLATIONS = {
|
|
|
198
201
|
"builder.error.publish": "\xC9chec de la publication",
|
|
199
202
|
"builder.error.unpublish": "\xC9chec de la d\xE9publication",
|
|
200
203
|
"builder.error.categoryCreate": "\xC9chec de la cr\xE9ation de cat\xE9gorie",
|
|
204
|
+
"builder.toast.saveSuccess": "Formulaire sauvegard\xE9",
|
|
205
|
+
"builder.toast.publishSuccess": "Formulaire publi\xE9",
|
|
206
|
+
"builder.toast.unpublishSuccess": "Formulaire d\xE9publi\xE9",
|
|
201
207
|
"builder.optionDefaultLabel": "Option",
|
|
202
208
|
"builder.optionsCount": "{count} options",
|
|
203
209
|
"builder.categoryModal.title": "Cr\xE9er une cat\xE9gorie",
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -29,7 +29,7 @@ function mergeRouteValues(primaryValues, secondaryValues) {
|
|
|
29
29
|
return merged;
|
|
30
30
|
}
|
|
31
31
|
function templateSegmentKey(value) {
|
|
32
|
-
const matched = value.match(/^\{([a-zA-Z0-9_]+)
|
|
32
|
+
const matched = value.match(/^\{([a-zA-Z0-9_]+)(?::[^}]+)?\}$/);
|
|
33
33
|
return matched?.[1] ?? null;
|
|
34
34
|
}
|
|
35
35
|
function toPathSegments(path) {
|
|
@@ -101,6 +101,19 @@ function withInferredMissingValues(base, inferred) {
|
|
|
101
101
|
}
|
|
102
102
|
return resolved;
|
|
103
103
|
}
|
|
104
|
+
function inferScopeSourcesFromPath(scopedRoutes, currentPath) {
|
|
105
|
+
const inferredSources = {};
|
|
106
|
+
for (const scopedRoute of Object.values(scopedRoutes ?? {})) {
|
|
107
|
+
const inferredScopeParams = inferParamsFromPath(scopedRoute.prefix, currentPath);
|
|
108
|
+
for (const [scopeParam, sourceParam] of Object.entries(scopedRoute.paramsFromRoute)) {
|
|
109
|
+
const inferredValue = inferredScopeParams[scopeParam];
|
|
110
|
+
if (inferredValue !== void 0 && inferredValue !== "" && inferredSources[sourceParam] === void 0) {
|
|
111
|
+
inferredSources[sourceParam] = inferredValue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return inferredSources;
|
|
116
|
+
}
|
|
104
117
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
105
118
|
const runtimeConfig = useRuntimeConfig();
|
|
106
119
|
const route = useRoute();
|
|
@@ -123,8 +136,11 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
123
136
|
...appRouteParams
|
|
124
137
|
}
|
|
125
138
|
);
|
|
126
|
-
const
|
|
127
|
-
|
|
139
|
+
const resolvedPath = composableRoutePath || appRoutePath;
|
|
140
|
+
const inferredFromBaseURL = inferParamsFromPath(publicConfig?.baseURL, resolvedPath);
|
|
141
|
+
const withBaseURLValues = withInferredMissingValues(mergedRouteValues, inferredFromBaseURL);
|
|
142
|
+
const inferredFromScopes = inferScopeSourcesFromPath(publicConfig?.scopedRoutes, resolvedPath);
|
|
143
|
+
return withInferredMissingValues(withBaseURLValues, inferredFromScopes);
|
|
128
144
|
};
|
|
129
145
|
const runtimeBaseURLParams = publicConfig?.baseURLParams;
|
|
130
146
|
const runtimeScopeParams = publicConfig?.scopeParams;
|
|
@@ -10,6 +10,10 @@ interface Props {
|
|
|
10
10
|
autosave?: boolean;
|
|
11
11
|
autosaveDelay?: number;
|
|
12
12
|
readonly?: boolean;
|
|
13
|
+
disableTitleInput?: boolean;
|
|
14
|
+
disableCategoryControl?: boolean;
|
|
15
|
+
disablePublishAction?: boolean;
|
|
16
|
+
defaultPublished?: boolean;
|
|
13
17
|
}
|
|
14
18
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
15
19
|
error: (value: string) => any;
|
|
@@ -34,6 +38,10 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
34
38
|
modelValue: Partial<FormForgeBuilderDraft>;
|
|
35
39
|
autosave: boolean;
|
|
36
40
|
autosaveDelay: number;
|
|
41
|
+
disableTitleInput: boolean;
|
|
42
|
+
disableCategoryControl: boolean;
|
|
43
|
+
disablePublishAction: boolean;
|
|
44
|
+
defaultPublished: boolean;
|
|
37
45
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
46
|
declare const _default: typeof __VLS_export;
|
|
39
47
|
export default _default;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "#imports";
|
|
3
3
|
import { useOverlay } from "@nuxt/ui/composables/useOverlay";
|
|
4
|
+
import { useToast } from "@nuxt/ui/composables/useToast";
|
|
4
5
|
import Draggable from "vuedraggable";
|
|
5
6
|
import {
|
|
6
7
|
FORM_FORGE_BUILDER_CONDITION_ACTIONS,
|
|
@@ -22,7 +23,11 @@ const props = defineProps({
|
|
|
22
23
|
modelValue: { type: Object, required: false, default: void 0 },
|
|
23
24
|
autosave: { type: Boolean, required: false, default: true },
|
|
24
25
|
autosaveDelay: { type: Number, required: false, default: 5e3 },
|
|
25
|
-
readonly: { type: Boolean, required: false, default: false }
|
|
26
|
+
readonly: { type: Boolean, required: false, default: false },
|
|
27
|
+
disableTitleInput: { type: Boolean, required: false, default: false },
|
|
28
|
+
disableCategoryControl: { type: Boolean, required: false, default: false },
|
|
29
|
+
disablePublishAction: { type: Boolean, required: false, default: false },
|
|
30
|
+
defaultPublished: { type: Boolean, required: false, default: false }
|
|
26
31
|
});
|
|
27
32
|
const emit = defineEmits(["update:modelValue", "save", "publish", "unpublish", "error"]);
|
|
28
33
|
const builderOptions = {
|
|
@@ -37,6 +42,7 @@ const builder = useFormForgeBuilder(builderOptions);
|
|
|
37
42
|
const { t } = useFormForgeI18n({
|
|
38
43
|
locale: () => props.locale
|
|
39
44
|
});
|
|
45
|
+
const toast = useToast();
|
|
40
46
|
const draft = builder.draft;
|
|
41
47
|
const isClientReady = ref(false);
|
|
42
48
|
const saving = builder.saving;
|
|
@@ -46,7 +52,7 @@ const lastSavedAt = builder.lastSavedAt;
|
|
|
46
52
|
const builderError = builder.error;
|
|
47
53
|
const overlay = useOverlay();
|
|
48
54
|
const categoryManager = useFormForgeCategory({
|
|
49
|
-
immediate:
|
|
55
|
+
immediate: !props.disableCategoryControl,
|
|
50
56
|
initialQuery: {
|
|
51
57
|
per_page: 200
|
|
52
58
|
},
|
|
@@ -63,6 +69,7 @@ const builderColumnElement = ref(null);
|
|
|
63
69
|
const railTop = ref(120);
|
|
64
70
|
const railLeft = ref(0);
|
|
65
71
|
const loadingRemoteForm = ref(false);
|
|
72
|
+
const isPublished = ref(props.defaultPublished);
|
|
66
73
|
let loadRequestId = 0;
|
|
67
74
|
const safePages = computed(() => {
|
|
68
75
|
const pages = draft.value?.pages;
|
|
@@ -143,6 +150,9 @@ const draftMutationIdentifier = computed(() => {
|
|
|
143
150
|
}
|
|
144
151
|
return null;
|
|
145
152
|
});
|
|
153
|
+
const showTopControls = computed(() => {
|
|
154
|
+
return !props.disableTitleInput || !props.disableCategoryControl;
|
|
155
|
+
});
|
|
146
156
|
function twoDigits(value) {
|
|
147
157
|
return String(value).padStart(2, "0");
|
|
148
158
|
}
|
|
@@ -438,6 +448,7 @@ function applyLoadedForm(schema) {
|
|
|
438
448
|
conditions: cloneValue(schema.conditions),
|
|
439
449
|
drafts: cloneValue(schema.drafts)
|
|
440
450
|
};
|
|
451
|
+
isPublished.value = schema.is_published === true;
|
|
441
452
|
}
|
|
442
453
|
async function loadFormIntoBuilder(key, version) {
|
|
443
454
|
const requestId = ++loadRequestId;
|
|
@@ -533,8 +544,22 @@ function setOptionLabel(field, optionIndex, value) {
|
|
|
533
544
|
}
|
|
534
545
|
async function save() {
|
|
535
546
|
try {
|
|
536
|
-
|
|
547
|
+
const shouldAutoPublish = props.defaultPublished;
|
|
548
|
+
await builder.save({
|
|
549
|
+
autoPublish: shouldAutoPublish
|
|
550
|
+
});
|
|
551
|
+
if (shouldAutoPublish) {
|
|
552
|
+
const wasPublished = isPublished.value;
|
|
553
|
+
isPublished.value = true;
|
|
554
|
+
if (!wasPublished) {
|
|
555
|
+
emit("publish", draft.value);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
537
558
|
emit("save", draft.value);
|
|
559
|
+
toast.add({
|
|
560
|
+
title: t("builder.toast.saveSuccess"),
|
|
561
|
+
color: "success"
|
|
562
|
+
});
|
|
538
563
|
} catch (caughtError) {
|
|
539
564
|
const message = caughtError instanceof Error ? caughtError.message : t("builder.error.save");
|
|
540
565
|
emit("error", message);
|
|
@@ -543,7 +568,12 @@ async function save() {
|
|
|
543
568
|
async function publish() {
|
|
544
569
|
try {
|
|
545
570
|
await builder.publish();
|
|
571
|
+
isPublished.value = true;
|
|
546
572
|
emit("publish", draft.value);
|
|
573
|
+
toast.add({
|
|
574
|
+
title: t("builder.toast.publishSuccess"),
|
|
575
|
+
color: "success"
|
|
576
|
+
});
|
|
547
577
|
} catch (caughtError) {
|
|
548
578
|
const message = caughtError instanceof Error ? caughtError.message : t("builder.error.publish");
|
|
549
579
|
emit("error", message);
|
|
@@ -552,12 +582,63 @@ async function publish() {
|
|
|
552
582
|
async function unpublish() {
|
|
553
583
|
try {
|
|
554
584
|
await builder.unpublish();
|
|
585
|
+
isPublished.value = false;
|
|
555
586
|
emit("unpublish", draft.value);
|
|
587
|
+
toast.add({
|
|
588
|
+
title: t("builder.toast.unpublishSuccess"),
|
|
589
|
+
color: "success"
|
|
590
|
+
});
|
|
556
591
|
} catch (caughtError) {
|
|
557
592
|
const message = caughtError instanceof Error ? caughtError.message : t("builder.error.unpublish");
|
|
558
593
|
emit("error", message);
|
|
559
594
|
}
|
|
560
595
|
}
|
|
596
|
+
const publishButtonLabel = computed(() => {
|
|
597
|
+
if (props.defaultPublished) {
|
|
598
|
+
return t("builder.publish");
|
|
599
|
+
}
|
|
600
|
+
return isPublished.value ? t("builder.unpublish") : t("builder.publish");
|
|
601
|
+
});
|
|
602
|
+
const publishButtonColor = computed(() => {
|
|
603
|
+
if (props.defaultPublished) {
|
|
604
|
+
return "primary";
|
|
605
|
+
}
|
|
606
|
+
return isPublished.value ? "neutral" : "primary";
|
|
607
|
+
});
|
|
608
|
+
const publishButtonVariant = computed(() => {
|
|
609
|
+
if (props.defaultPublished) {
|
|
610
|
+
return "solid";
|
|
611
|
+
}
|
|
612
|
+
return isPublished.value ? "soft" : "solid";
|
|
613
|
+
});
|
|
614
|
+
const canTogglePublish = computed(() => {
|
|
615
|
+
if (props.readonly) {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
if (props.defaultPublished && isPublished.value) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
if (draftMutationIdentifier.value === null) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
if (!isPublished.value && !publishable.value) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
return true;
|
|
628
|
+
});
|
|
629
|
+
const toolbarActionsClass = computed(() => {
|
|
630
|
+
if (showTopControls.value) {
|
|
631
|
+
return ["builder-toolbar-actions"];
|
|
632
|
+
}
|
|
633
|
+
return ["builder-toolbar-actions", "builder-toolbar-actions--compact"];
|
|
634
|
+
});
|
|
635
|
+
async function togglePublishState() {
|
|
636
|
+
if (!props.defaultPublished && isPublished.value) {
|
|
637
|
+
await unpublish();
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
await publish();
|
|
641
|
+
}
|
|
561
642
|
function removeField(page, fieldKey) {
|
|
562
643
|
builder.removeField(page.page_key, fieldKey);
|
|
563
644
|
if (selectedFieldKey.value === fieldKey) {
|
|
@@ -609,6 +690,31 @@ function duplicateField(page, fieldKey) {
|
|
|
609
690
|
builder.duplicateField(page.page_key, fieldKey);
|
|
610
691
|
builder.normalizeFieldLocations();
|
|
611
692
|
}
|
|
693
|
+
watch(() => props.modelValue, (value) => {
|
|
694
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
695
|
+
isPublished.value = props.defaultPublished;
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const candidate = value;
|
|
699
|
+
if (candidate.is_published === true) {
|
|
700
|
+
isPublished.value = true;
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (candidate.is_published === false) {
|
|
704
|
+
isPublished.value = false;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
isPublished.value = props.defaultPublished;
|
|
708
|
+
}, {
|
|
709
|
+
immediate: true,
|
|
710
|
+
deep: false
|
|
711
|
+
});
|
|
712
|
+
watch(() => props.defaultPublished, (value) => {
|
|
713
|
+
if (typeof props.modelValue === "object" && props.modelValue !== null && "is_published" in props.modelValue) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
isPublished.value = value;
|
|
717
|
+
});
|
|
612
718
|
watch(() => draft.value, (value) => {
|
|
613
719
|
emit("update:modelValue", value);
|
|
614
720
|
}, {
|
|
@@ -667,13 +773,20 @@ watch(() => selectedFieldKey.value, () => {
|
|
|
667
773
|
class="builder-card"
|
|
668
774
|
>
|
|
669
775
|
<div class="builder-toolbar">
|
|
670
|
-
<div
|
|
776
|
+
<div
|
|
777
|
+
v-if="showTopControls"
|
|
778
|
+
class="builder-toolbar-grid"
|
|
779
|
+
>
|
|
671
780
|
<UInput
|
|
781
|
+
v-if="!disableTitleInput"
|
|
672
782
|
v-model="draftTitle"
|
|
673
783
|
:disabled="readonly"
|
|
674
784
|
:placeholder="t('builder.formTitlePlaceholder')"
|
|
675
785
|
/>
|
|
676
|
-
<div
|
|
786
|
+
<div
|
|
787
|
+
v-if="!disableCategoryControl"
|
|
788
|
+
class="builder-category-control"
|
|
789
|
+
>
|
|
677
790
|
<USelect
|
|
678
791
|
v-model="draftCategorySelectValue"
|
|
679
792
|
:items="categorySelectItems"
|
|
@@ -692,7 +805,7 @@ watch(() => selectedFieldKey.value, () => {
|
|
|
692
805
|
</div>
|
|
693
806
|
</div>
|
|
694
807
|
|
|
695
|
-
<div class="
|
|
808
|
+
<div :class="toolbarActionsClass">
|
|
696
809
|
<div class="builder-status">
|
|
697
810
|
<span v-if="loadingRemoteForm">{{ t("builder.loadingForm") }}</span>
|
|
698
811
|
<span v-if="formattedLastSavedAt !== null">{{ t("builder.lastSave", { value: formattedLastSavedAt }) }}</span>
|
|
@@ -711,21 +824,14 @@ watch(() => selectedFieldKey.value, () => {
|
|
|
711
824
|
{{ t("builder.save") }}
|
|
712
825
|
</UButton>
|
|
713
826
|
<UButton
|
|
714
|
-
|
|
715
|
-
:
|
|
716
|
-
:
|
|
717
|
-
@click="publish"
|
|
718
|
-
>
|
|
719
|
-
{{ t("builder.publish") }}
|
|
720
|
-
</UButton>
|
|
721
|
-
<UButton
|
|
722
|
-
color="neutral"
|
|
723
|
-
variant="soft"
|
|
827
|
+
v-if="!disablePublishAction && !defaultPublished"
|
|
828
|
+
:color="publishButtonColor"
|
|
829
|
+
:variant="publishButtonVariant"
|
|
724
830
|
:loading="publishing"
|
|
725
|
-
:disabled="
|
|
726
|
-
@click="
|
|
831
|
+
:disabled="!canTogglePublish"
|
|
832
|
+
@click="togglePublishState"
|
|
727
833
|
>
|
|
728
|
-
{{
|
|
834
|
+
{{ publishButtonLabel }}
|
|
729
835
|
</UButton>
|
|
730
836
|
</div>
|
|
731
837
|
</div>
|
|
@@ -1155,5 +1261,5 @@ watch(() => selectedFieldKey.value, () => {
|
|
|
1155
1261
|
</template>
|
|
1156
1262
|
|
|
1157
1263
|
<style scoped>
|
|
1158
|
-
.builder-root{width:100%}.builder-layout{align-items:start;display:grid;gap:1rem;grid-template-columns:minmax(0,1fr) auto;width:100%}.builder-column{display:grid;gap:1.5rem;min-width:0;width:100%}.builder-card{border-radius:16px}.builder-toolbar{display:grid;gap:1rem}.builder-toolbar-grid{display:grid;gap:.85rem;grid-template-columns:1fr}.builder-category-control{align-items:center;display:grid;gap:.5rem;grid-template-columns:minmax(0,1fr) auto}.builder-toolbar-actions{align-items:center;border-top:1px solid var(--ui-border-muted);display:flex;flex-wrap:wrap;gap:.75rem;justify-content:space-between;padding-top:.85rem}.builder-status{color:var(--ui-text-muted);font-size:.82rem}.builder-error{color:var(--ui-error);margin-left:.5rem}.builder-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.builder-stack{display:grid;gap:1rem}.pages-stack{gap:2.25rem}.drag-handle{color:var(--ui-text-muted);cursor:grab;padding-top:.4rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.page-block{display:grid;gap:1rem}.page-meta-card{border-radius:16px}.page-chip-row{margin-bottom:.65rem}.page-header{align-items:start;display:grid;gap:.75rem;grid-template-columns:auto minmax(0,1fr) auto}.page-header-main{display:grid;gap:.6rem;min-width:0}.page-actions{align-items:center;display:inline-flex;gap:.25rem}.page-questions-stack{gap:1rem}.field-card{border-left:4px solid transparent;border-radius:14px;cursor:pointer}.field-card--active{border-left-color:var(--ui-primary)}.field-shell{display:grid;gap:.85rem}.field-head{align-items:center;display:grid;gap:.75rem;grid-template-columns:auto minmax(0,1fr) minmax(220px,280px)}.field-controls{align-items:center;display:flex;flex-wrap:wrap;gap:.35rem}.field-required-switch{align-items:center;color:var(--ui-text-toned);display:inline-flex;font-size:.82rem;gap:.5rem;margin-left:auto}.field-preview{color:var(--ui-text-muted);font-size:.84rem;margin:0}.field-inline-preview{background:var(--ui-bg-muted);border-radius:10px;display:grid;gap:.5rem;padding:.75rem}.field-inline-preview-label{color:var(--ui-text-muted);font-size:.8rem;margin:0}.field-inline-preview-line{border-bottom:1px dashed var(--ui-border-accented);height:1.4rem;width:min(32rem,100%)}.field-inline-preview-textarea{display:grid;gap:.5rem;width:min(32rem,100%)}.field-inline-preview-textarea>span{border-bottom:1px dashed var(--ui-border-accented);display:block;height:1.2rem}.field-advanced{border-top:1px solid var(--ui-border-muted);padding-top:.75rem}.field-advanced-summary{color:var(--ui-text-toned);cursor:pointer;font-size:.82rem;font-weight:500}.field-advanced-body{background:var(--ui-bg-elevated);border-radius:12px;display:grid;gap:.75rem;margin-top:.75rem;padding:.75rem}.field-advanced-grid{display:grid;gap:.65rem}.field-toggle-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.field-options,.field-toggle-grid{display:grid;gap:.6rem}.field-option-row{align-items:center;display:grid;gap:.45rem;grid-template-columns:minmax(0,1fr) auto}.field-numbers{grid-template-columns:repeat(3,minmax(0,1fr))}.field-file,.field-numbers{display:grid;gap:.55rem}.conditions-card{border-radius:16px}.builder-row{align-items:center;display:flex;gap:.75rem;justify-content:space-between}.conditions-title{font-size:.95rem;font-weight:600}.condition-card{border:1px solid var(--ui-border-muted);border-radius:12px;display:grid;gap:.75rem;padding:.75rem}.condition-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.builder-rail-column{display:flex;justify-content:center;width:4.25rem}.builder-rail{background:var(--ui-bg);border:1px solid var(--ui-border-muted);border-radius:14px;box-shadow:0 8px 24px color-mix(in srgb,var(--ui-text) 8%,transparent);display:flex;flex-direction:column;gap:.5rem;padding:.5rem;position:fixed;transition:top .12s ease,left .12s ease;z-index:20}.rail-action{height:2.75rem;justify-content:center;width:2.75rem}@media (min-width:1024px){.builder-toolbar-grid{grid-template-columns:1.2fr .8fr}}@media (max-width:1024px){.builder-layout{display:block}.builder-rail{bottom:.85rem;flex-direction:row;left:auto!important;position:fixed;right:.85rem;top:auto!important}}@media (max-width:768px){.page-header{grid-template-columns:minmax(0,1fr) auto}.page-drag-handle{display:none}.field-head{grid-template-columns:minmax(0,1fr)}.field-drag-handle{display:none}.field-numbers,.field-toggle-grid{grid-template-columns:1fr}.field-required-switch{margin-left:0}}
|
|
1264
|
+
.builder-root{width:100%}.builder-layout{align-items:start;display:grid;gap:1rem;grid-template-columns:minmax(0,1fr) auto;width:100%}.builder-column{display:grid;gap:1.5rem;min-width:0;width:100%}.builder-card{border-radius:16px}.builder-toolbar{display:grid;gap:1rem}.builder-toolbar-grid{display:grid;gap:.85rem;grid-template-columns:1fr}.builder-category-control{align-items:center;display:grid;gap:.5rem;grid-template-columns:minmax(0,1fr) auto}.builder-toolbar-actions{align-items:center;border-top:1px solid var(--ui-border-muted);display:flex;flex-wrap:wrap;gap:.75rem;justify-content:space-between;padding-top:.85rem}.builder-toolbar-actions--compact{border-top:none;padding-top:0}.builder-status{color:var(--ui-text-muted);font-size:.82rem}.builder-error{color:var(--ui-error);margin-left:.5rem}.builder-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.builder-stack{display:grid;gap:1rem}.pages-stack{gap:2.25rem}.drag-handle{color:var(--ui-text-muted);cursor:grab;padding-top:.4rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.page-block{display:grid;gap:1rem}.page-meta-card{border-radius:16px}.page-chip-row{margin-bottom:.65rem}.page-header{align-items:start;display:grid;gap:.75rem;grid-template-columns:auto minmax(0,1fr) auto}.page-header-main{display:grid;gap:.6rem;min-width:0}.page-actions{align-items:center;display:inline-flex;gap:.25rem}.page-questions-stack{gap:1rem}.field-card{border-left:4px solid transparent;border-radius:14px;cursor:pointer}.field-card--active{border-left-color:var(--ui-primary)}.field-shell{display:grid;gap:.85rem}.field-head{align-items:center;display:grid;gap:.75rem;grid-template-columns:auto minmax(0,1fr) minmax(220px,280px)}.field-controls{align-items:center;display:flex;flex-wrap:wrap;gap:.35rem}.field-required-switch{align-items:center;color:var(--ui-text-toned);display:inline-flex;font-size:.82rem;gap:.5rem;margin-left:auto}.field-preview{color:var(--ui-text-muted);font-size:.84rem;margin:0}.field-inline-preview{background:var(--ui-bg-muted);border-radius:10px;display:grid;gap:.5rem;padding:.75rem}.field-inline-preview-label{color:var(--ui-text-muted);font-size:.8rem;margin:0}.field-inline-preview-line{border-bottom:1px dashed var(--ui-border-accented);height:1.4rem;width:min(32rem,100%)}.field-inline-preview-textarea{display:grid;gap:.5rem;width:min(32rem,100%)}.field-inline-preview-textarea>span{border-bottom:1px dashed var(--ui-border-accented);display:block;height:1.2rem}.field-advanced{border-top:1px solid var(--ui-border-muted);padding-top:.75rem}.field-advanced-summary{color:var(--ui-text-toned);cursor:pointer;font-size:.82rem;font-weight:500}.field-advanced-body{background:var(--ui-bg-elevated);border-radius:12px;display:grid;gap:.75rem;margin-top:.75rem;padding:.75rem}.field-advanced-grid{display:grid;gap:.65rem}.field-toggle-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.field-options,.field-toggle-grid{display:grid;gap:.6rem}.field-option-row{align-items:center;display:grid;gap:.45rem;grid-template-columns:minmax(0,1fr) auto}.field-numbers{grid-template-columns:repeat(3,minmax(0,1fr))}.field-file,.field-numbers{display:grid;gap:.55rem}.conditions-card{border-radius:16px}.builder-row{align-items:center;display:flex;gap:.75rem;justify-content:space-between}.conditions-title{font-size:.95rem;font-weight:600}.condition-card{border:1px solid var(--ui-border-muted);border-radius:12px;display:grid;gap:.75rem;padding:.75rem}.condition-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.builder-rail-column{display:flex;justify-content:center;width:4.25rem}.builder-rail{background:var(--ui-bg);border:1px solid var(--ui-border-muted);border-radius:14px;box-shadow:0 8px 24px color-mix(in srgb,var(--ui-text) 8%,transparent);display:flex;flex-direction:column;gap:.5rem;padding:.5rem;position:fixed;transition:top .12s ease,left .12s ease;z-index:20}.rail-action{height:2.75rem;justify-content:center;width:2.75rem}@media (min-width:1024px){.builder-toolbar-grid{grid-template-columns:1.2fr .8fr}}@media (max-width:1024px){.builder-layout{display:block}.builder-rail{bottom:.85rem;flex-direction:row;left:auto!important;position:fixed;right:.85rem;top:auto!important}}@media (max-width:768px){.page-header{grid-template-columns:minmax(0,1fr) auto}.page-drag-handle{display:none}.field-head{grid-template-columns:minmax(0,1fr)}.field-drag-handle{display:none}.field-numbers,.field-toggle-grid{grid-template-columns:1fr}.field-required-switch{margin-left:0}}
|
|
1159
1265
|
</style>
|
|
@@ -10,6 +10,10 @@ interface Props {
|
|
|
10
10
|
autosave?: boolean;
|
|
11
11
|
autosaveDelay?: number;
|
|
12
12
|
readonly?: boolean;
|
|
13
|
+
disableTitleInput?: boolean;
|
|
14
|
+
disableCategoryControl?: boolean;
|
|
15
|
+
disablePublishAction?: boolean;
|
|
16
|
+
defaultPublished?: boolean;
|
|
13
17
|
}
|
|
14
18
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
15
19
|
error: (value: string) => any;
|
|
@@ -34,6 +38,10 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
34
38
|
modelValue: Partial<FormForgeBuilderDraft>;
|
|
35
39
|
autosave: boolean;
|
|
36
40
|
autosaveDelay: number;
|
|
41
|
+
disableTitleInput: boolean;
|
|
42
|
+
disableCategoryControl: boolean;
|
|
43
|
+
disablePublishAction: boolean;
|
|
44
|
+
defaultPublished: boolean;
|
|
37
45
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
46
|
declare const _default: typeof __VLS_export;
|
|
39
47
|
export default _default;
|
|
@@ -5,6 +5,12 @@ interface FormForgeValidationError {
|
|
|
5
5
|
}
|
|
6
6
|
type FormForgeValidationHandler = (state: FormForgeSubmissionPayload) => FormForgeValidationError[] | Promise<FormForgeValidationError[]>;
|
|
7
7
|
type FormForgeProgressVariant = 'stepper' | 'progress';
|
|
8
|
+
type FormForgeValidateEvent = 'input' | 'change' | 'blur';
|
|
9
|
+
interface FormForgeExposedError {
|
|
10
|
+
id?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
8
14
|
interface Props {
|
|
9
15
|
schema?: FormForgeFormSchema | {
|
|
10
16
|
value: FormForgeFormSchema | null;
|
|
@@ -30,8 +36,25 @@ interface Props {
|
|
|
30
36
|
showProgress?: boolean;
|
|
31
37
|
progressVariant?: FormForgeProgressVariant;
|
|
32
38
|
showAlertOnError?: boolean;
|
|
39
|
+
validateOn?: FormForgeValidateEvent[];
|
|
40
|
+
validateOnBlur?: boolean;
|
|
33
41
|
}
|
|
34
|
-
|
|
42
|
+
type FormForgeValidateOptions = {
|
|
43
|
+
name?: string | string[];
|
|
44
|
+
silent?: boolean;
|
|
45
|
+
nested?: boolean;
|
|
46
|
+
transform?: boolean;
|
|
47
|
+
};
|
|
48
|
+
declare function validateForm(options?: FormForgeValidateOptions): Promise<boolean>;
|
|
49
|
+
declare function validateField(name: string): Promise<boolean>;
|
|
50
|
+
declare function clearErrors(path?: string | RegExp): void;
|
|
51
|
+
declare function getErrors(path?: string | RegExp): FormForgeExposedError[];
|
|
52
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {
|
|
53
|
+
validate: typeof validateForm;
|
|
54
|
+
validateField: typeof validateField;
|
|
55
|
+
clearErrors: typeof clearErrors;
|
|
56
|
+
getErrors: typeof getErrors;
|
|
57
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
35
58
|
submit: (value: FormForgeSubmissionPayload) => any;
|
|
36
59
|
error: (value: string) => any;
|
|
37
60
|
"update:modelValue": (value: FormForgeSubmissionPayload) => any;
|
|
@@ -66,6 +89,8 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
66
89
|
showProgress: boolean;
|
|
67
90
|
progressVariant: FormForgeProgressVariant;
|
|
68
91
|
showAlertOnError: boolean;
|
|
92
|
+
validateOn: FormForgeValidateEvent[];
|
|
93
|
+
validateOnBlur: boolean;
|
|
69
94
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
70
95
|
declare const _default: typeof __VLS_export;
|
|
71
96
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, ref, watch } from "#imports";
|
|
2
|
+
import { computed, ref, useTemplateRef, watch } from "#imports";
|
|
3
3
|
import { getLocalTimeZone, parseAbsoluteToLocal, parseDate, parseDateTime, parseTime } from "@internationalized/date";
|
|
4
4
|
import UCheckbox from "@nuxt/ui/components/Checkbox.vue";
|
|
5
5
|
import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
|
|
@@ -37,12 +37,15 @@ const props = defineProps({
|
|
|
37
37
|
clearAfterSubmit: { type: Boolean, required: false, default: false },
|
|
38
38
|
showProgress: { type: Boolean, required: false, default: false },
|
|
39
39
|
progressVariant: { type: String, required: false, default: "stepper" },
|
|
40
|
-
showAlertOnError: { type: Boolean, required: false, default: false }
|
|
40
|
+
showAlertOnError: { type: Boolean, required: false, default: false },
|
|
41
|
+
validateOn: { type: Array, required: false, default: void 0 },
|
|
42
|
+
validateOnBlur: { type: Boolean, required: false, default: void 0 }
|
|
41
43
|
});
|
|
42
44
|
const { t } = useFormForgeI18n({
|
|
43
45
|
locale: () => props.clientConfig?.locale
|
|
44
46
|
});
|
|
45
47
|
const emit = defineEmits(["update:modelValue", "submit", "submitted", "error"]);
|
|
48
|
+
const rendererForm = useTemplateRef("rendererForm");
|
|
46
49
|
function isRefLike(value) {
|
|
47
50
|
if (typeof value !== "object" || value === null) {
|
|
48
51
|
return false;
|
|
@@ -79,8 +82,11 @@ function unwrapZodSchemaProp(value) {
|
|
|
79
82
|
}
|
|
80
83
|
return value;
|
|
81
84
|
}
|
|
82
|
-
const
|
|
83
|
-
return props.
|
|
85
|
+
const usesExternalModel = computed(() => {
|
|
86
|
+
return props.modelValue !== void 0;
|
|
87
|
+
});
|
|
88
|
+
const usesExternalSchema = computed(() => {
|
|
89
|
+
return props.schema !== void 0;
|
|
84
90
|
});
|
|
85
91
|
const internalFormKey = computed(() => {
|
|
86
92
|
if (props.formKey === void 0 || props.formKey.trim() === "") {
|
|
@@ -106,10 +112,10 @@ const internalSubmit = useFormForgeSubmit({
|
|
|
106
112
|
const submittedResponse = ref(null);
|
|
107
113
|
const rendererErrors = ref([]);
|
|
108
114
|
watch(
|
|
109
|
-
() => [
|
|
110
|
-
async ([
|
|
115
|
+
() => [usesExternalSchema.value, internalFormKey.value, props.formVersion],
|
|
116
|
+
async ([externalSchema, formKey]) => {
|
|
111
117
|
submittedResponse.value = null;
|
|
112
|
-
if (
|
|
118
|
+
if (externalSchema || formKey === "") {
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
121
|
await internalForm.fetchSchema().catch(() => {
|
|
@@ -120,27 +126,27 @@ watch(
|
|
|
120
126
|
}
|
|
121
127
|
);
|
|
122
128
|
function getResolvedSchema() {
|
|
123
|
-
if (
|
|
129
|
+
if (usesExternalSchema.value) {
|
|
124
130
|
return unwrapSchemaProp(props.schema);
|
|
125
131
|
}
|
|
126
132
|
return internalForm.schema.value;
|
|
127
133
|
}
|
|
128
134
|
function getResolvedZodSchema() {
|
|
129
|
-
if (
|
|
135
|
+
if (usesExternalSchema.value) {
|
|
130
136
|
return unwrapZodSchemaProp(props.zodSchema);
|
|
131
137
|
}
|
|
132
138
|
return internalForm.zodSchema.value;
|
|
133
139
|
}
|
|
134
140
|
const formState = computed({
|
|
135
141
|
get: () => {
|
|
136
|
-
const value =
|
|
142
|
+
const value = usesExternalModel.value ? unwrapModelValueProp(props.modelValue) : internalForm.state.value;
|
|
137
143
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
138
144
|
return {};
|
|
139
145
|
}
|
|
140
146
|
return value;
|
|
141
147
|
},
|
|
142
148
|
set: (value) => {
|
|
143
|
-
if (
|
|
149
|
+
if (usesExternalModel.value) {
|
|
144
150
|
emit("update:modelValue", value);
|
|
145
151
|
return;
|
|
146
152
|
}
|
|
@@ -635,6 +641,68 @@ function onFormError(event) {
|
|
|
635
641
|
rendererErrors.value = errors;
|
|
636
642
|
navigateToFirstErrorPage(errors);
|
|
637
643
|
}
|
|
644
|
+
async function validateForm(options = {}) {
|
|
645
|
+
const formInstance = rendererForm.value;
|
|
646
|
+
if (formInstance === void 0) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
await formInstance.validate(options);
|
|
651
|
+
rendererErrors.value = [];
|
|
652
|
+
return true;
|
|
653
|
+
} catch {
|
|
654
|
+
const errors = formInstance.getErrors();
|
|
655
|
+
rendererErrors.value = errors.map((error) => ({
|
|
656
|
+
id: error.id,
|
|
657
|
+
name: error.name,
|
|
658
|
+
message: error.message
|
|
659
|
+
}));
|
|
660
|
+
navigateToFirstErrorPage(rendererErrors.value);
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function validateField(name) {
|
|
665
|
+
return validateForm({
|
|
666
|
+
name
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function clearErrors(path) {
|
|
670
|
+
const formInstance = rendererForm.value;
|
|
671
|
+
if (formInstance === void 0) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
formInstance.clear(path);
|
|
675
|
+
rendererErrors.value = [];
|
|
676
|
+
}
|
|
677
|
+
function getErrors(path) {
|
|
678
|
+
const formInstance = rendererForm.value;
|
|
679
|
+
if (formInstance === void 0) {
|
|
680
|
+
return [];
|
|
681
|
+
}
|
|
682
|
+
return formInstance.getErrors(path);
|
|
683
|
+
}
|
|
684
|
+
const shouldValidateFieldOnBlur = computed(() => {
|
|
685
|
+
if (props.validateOnBlur !== void 0) {
|
|
686
|
+
return props.validateOnBlur;
|
|
687
|
+
}
|
|
688
|
+
if (Array.isArray(props.validateOn)) {
|
|
689
|
+
return props.validateOn.includes("blur");
|
|
690
|
+
}
|
|
691
|
+
return usesExternalModel.value;
|
|
692
|
+
});
|
|
693
|
+
function onFieldBlur(fieldName) {
|
|
694
|
+
if (!shouldValidateFieldOnBlur.value) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
validateField(fieldName).catch(() => {
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
defineExpose({
|
|
701
|
+
validate: validateForm,
|
|
702
|
+
validateField,
|
|
703
|
+
clearErrors,
|
|
704
|
+
getErrors
|
|
705
|
+
});
|
|
638
706
|
function hasDateMethod(value) {
|
|
639
707
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
640
708
|
return false;
|
|
@@ -841,7 +909,7 @@ function getComponentModelValue(field) {
|
|
|
841
909
|
}
|
|
842
910
|
function getComponentProps(field, page) {
|
|
843
911
|
const metaUi = getFieldMetaUi(field);
|
|
844
|
-
const isDisabled = props.disabled || !
|
|
912
|
+
const isDisabled = props.disabled || !usesExternalModel.value && internalSubmit.submitting.value || isFieldDisabled(field, page);
|
|
845
913
|
const componentProps = {
|
|
846
914
|
name: field.name,
|
|
847
915
|
required: isFieldRequired(field),
|
|
@@ -900,7 +968,7 @@ function onFieldModelUpdate(field, nextValue) {
|
|
|
900
968
|
async function onSubmit() {
|
|
901
969
|
rendererErrors.value = [];
|
|
902
970
|
emit("submit", formState.value);
|
|
903
|
-
if (
|
|
971
|
+
if (usesExternalModel.value) {
|
|
904
972
|
return;
|
|
905
973
|
}
|
|
906
974
|
if (internalFormKey.value === "") {
|
|
@@ -943,9 +1011,11 @@ async function onSubmit() {
|
|
|
943
1011
|
|
|
944
1012
|
<template>
|
|
945
1013
|
<UForm
|
|
1014
|
+
ref="rendererForm"
|
|
946
1015
|
:state="formState"
|
|
947
1016
|
:schema="getResolvedZodSchema()"
|
|
948
|
-
:validate="validate"
|
|
1017
|
+
:validate="props.validate"
|
|
1018
|
+
:validate-on="props.validateOn"
|
|
949
1019
|
@submit="onSubmit"
|
|
950
1020
|
@error="onFormError"
|
|
951
1021
|
>
|
|
@@ -967,14 +1037,14 @@ async function onSubmit() {
|
|
|
967
1037
|
/>
|
|
968
1038
|
|
|
969
1039
|
<UAlert
|
|
970
|
-
v-if="!
|
|
1040
|
+
v-if="!usesExternalSchema && internalForm.loading.value"
|
|
971
1041
|
color="neutral"
|
|
972
1042
|
variant="soft"
|
|
973
1043
|
:title="t('renderer.loadingForm')"
|
|
974
1044
|
/>
|
|
975
1045
|
|
|
976
1046
|
<UAlert
|
|
977
|
-
v-if="!
|
|
1047
|
+
v-if="!usesExternalSchema && internalForm.error.value"
|
|
978
1048
|
color="error"
|
|
979
1049
|
variant="soft"
|
|
980
1050
|
:title="t('renderer.error.loadForm')"
|
|
@@ -982,7 +1052,7 @@ async function onSubmit() {
|
|
|
982
1052
|
/>
|
|
983
1053
|
|
|
984
1054
|
<UAlert
|
|
985
|
-
v-if="!
|
|
1055
|
+
v-if="!usesExternalModel && internalSubmit.error.value"
|
|
986
1056
|
color="error"
|
|
987
1057
|
variant="soft"
|
|
988
1058
|
:title="t('renderer.error.submit')"
|
|
@@ -1009,7 +1079,7 @@ async function onSubmit() {
|
|
|
1009
1079
|
</UAlert>
|
|
1010
1080
|
|
|
1011
1081
|
<UAlert
|
|
1012
|
-
v-if="!
|
|
1082
|
+
v-if="!usesExternalModel && submittedResponse !== null"
|
|
1013
1083
|
color="success"
|
|
1014
1084
|
variant="soft"
|
|
1015
1085
|
:title="t('renderer.alert.submitted')"
|
|
@@ -1050,6 +1120,7 @@ async function onSubmit() {
|
|
|
1050
1120
|
:help="field.help_text"
|
|
1051
1121
|
:required="isFieldRequired(field)"
|
|
1052
1122
|
:ui="getFieldMetaUi(field).formField"
|
|
1123
|
+
@focusout="() => onFieldBlur(field.name)"
|
|
1053
1124
|
>
|
|
1054
1125
|
<UInput
|
|
1055
1126
|
v-if="field.type === 'text' || field.type === 'email'"
|
|
@@ -1162,7 +1233,7 @@ async function onSubmit() {
|
|
|
1162
1233
|
</UButton>
|
|
1163
1234
|
|
|
1164
1235
|
<UButton
|
|
1165
|
-
v-else-if="!
|
|
1236
|
+
v-else-if="!usesExternalModel && showSubmit"
|
|
1166
1237
|
type="submit"
|
|
1167
1238
|
:loading="internalSubmit.submitting.value"
|
|
1168
1239
|
:disabled="internalForm.loading.value || getResolvedSchema() === null"
|
|
@@ -1172,7 +1243,7 @@ async function onSubmit() {
|
|
|
1172
1243
|
</div>
|
|
1173
1244
|
|
|
1174
1245
|
<div
|
|
1175
|
-
v-else-if="!
|
|
1246
|
+
v-else-if="!usesExternalModel && showSubmit"
|
|
1176
1247
|
class="flex justify-end"
|
|
1177
1248
|
>
|
|
1178
1249
|
<UButton
|
|
@@ -5,6 +5,12 @@ interface FormForgeValidationError {
|
|
|
5
5
|
}
|
|
6
6
|
type FormForgeValidationHandler = (state: FormForgeSubmissionPayload) => FormForgeValidationError[] | Promise<FormForgeValidationError[]>;
|
|
7
7
|
type FormForgeProgressVariant = 'stepper' | 'progress';
|
|
8
|
+
type FormForgeValidateEvent = 'input' | 'change' | 'blur';
|
|
9
|
+
interface FormForgeExposedError {
|
|
10
|
+
id?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
8
14
|
interface Props {
|
|
9
15
|
schema?: FormForgeFormSchema | {
|
|
10
16
|
value: FormForgeFormSchema | null;
|
|
@@ -30,8 +36,25 @@ interface Props {
|
|
|
30
36
|
showProgress?: boolean;
|
|
31
37
|
progressVariant?: FormForgeProgressVariant;
|
|
32
38
|
showAlertOnError?: boolean;
|
|
39
|
+
validateOn?: FormForgeValidateEvent[];
|
|
40
|
+
validateOnBlur?: boolean;
|
|
33
41
|
}
|
|
34
|
-
|
|
42
|
+
type FormForgeValidateOptions = {
|
|
43
|
+
name?: string | string[];
|
|
44
|
+
silent?: boolean;
|
|
45
|
+
nested?: boolean;
|
|
46
|
+
transform?: boolean;
|
|
47
|
+
};
|
|
48
|
+
declare function validateForm(options?: FormForgeValidateOptions): Promise<boolean>;
|
|
49
|
+
declare function validateField(name: string): Promise<boolean>;
|
|
50
|
+
declare function clearErrors(path?: string | RegExp): void;
|
|
51
|
+
declare function getErrors(path?: string | RegExp): FormForgeExposedError[];
|
|
52
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {
|
|
53
|
+
validate: typeof validateForm;
|
|
54
|
+
validateField: typeof validateField;
|
|
55
|
+
clearErrors: typeof clearErrors;
|
|
56
|
+
getErrors: typeof getErrors;
|
|
57
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
35
58
|
submit: (value: FormForgeSubmissionPayload) => any;
|
|
36
59
|
error: (value: string) => any;
|
|
37
60
|
"update:modelValue": (value: FormForgeSubmissionPayload) => any;
|
|
@@ -66,6 +89,8 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
66
89
|
showProgress: boolean;
|
|
67
90
|
progressVariant: FormForgeProgressVariant;
|
|
68
91
|
showAlertOnError: boolean;
|
|
92
|
+
validateOn: FormForgeValidateEvent[];
|
|
93
|
+
validateOnBlur: boolean;
|
|
69
94
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
70
95
|
declare const _default: typeof __VLS_export;
|
|
71
96
|
export default _default;
|
|
@@ -10,6 +10,8 @@ export interface FormForgeManagementCreateInput {
|
|
|
10
10
|
category?: string | null;
|
|
11
11
|
meta?: FormForgeJsonObject;
|
|
12
12
|
api?: FormForgeJsonObject;
|
|
13
|
+
auto_publish?: boolean;
|
|
14
|
+
autoPublish?: boolean;
|
|
13
15
|
}
|
|
14
16
|
export interface FormForgeManagementPatchInput {
|
|
15
17
|
title?: string;
|
|
@@ -20,6 +22,8 @@ export interface FormForgeManagementPatchInput {
|
|
|
20
22
|
category?: string | null;
|
|
21
23
|
meta?: FormForgeJsonObject;
|
|
22
24
|
api?: FormForgeJsonObject;
|
|
25
|
+
auto_publish?: boolean;
|
|
26
|
+
autoPublish?: boolean;
|
|
23
27
|
}
|
|
24
28
|
export type FormForgeManagementForm = FormForgeJsonObject & {
|
|
25
29
|
id?: string | number;
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evanschleret/formforgeclient",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Nuxt module and runtime client for FormForge",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/EvanSchleret/formforgeclient.git"
|
|
10
|
+
},
|
|
7
11
|
"main": "./dist/module.cjs",
|
|
8
12
|
"module": "./dist/module.mjs",
|
|
9
13
|
"types": "./dist/module.d.ts",
|