@form-engine-ts/react 2.0.0 → 2.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/README.md +12 -7
- package/dist/index.cjs +568 -499
- package/dist/index.d.cts +69 -27
- package/dist/index.d.ts +69 -27
- package/dist/index.js +570 -501
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
// src/builder.tsx
|
|
2
2
|
import {
|
|
3
|
-
populateSchemaTranslations
|
|
4
|
-
sanitizeSchema
|
|
3
|
+
populateSchemaTranslations
|
|
5
4
|
} from "@form-engine-ts/core";
|
|
6
5
|
import { useState } from "react";
|
|
7
6
|
|
|
8
7
|
// src/hooks/useFormBuilder.ts
|
|
9
8
|
import {
|
|
9
|
+
transformFieldType,
|
|
10
10
|
validateFormSchema
|
|
11
11
|
} from "@form-engine-ts/core";
|
|
12
12
|
import { useCallback, useMemo } from "react";
|
|
13
13
|
var DEFAULT_PREFIXES = { field: "q", option: "opt", page: "page" };
|
|
14
|
+
var CHOICE_TYPES = ["select", "radio", "multi-select"];
|
|
14
15
|
function defaultIdFactory(kind, existingIds) {
|
|
15
16
|
const prefix = DEFAULT_PREFIXES[kind];
|
|
16
17
|
let id;
|
|
17
|
-
do
|
|
18
|
+
do
|
|
18
19
|
id = `${prefix}_${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
19
|
-
|
|
20
|
+
while (existingIds.has(id));
|
|
20
21
|
return id;
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
+
function defaultCreateField(type, id) {
|
|
23
24
|
const base = { id, title: "New question", required: false };
|
|
24
|
-
if (type === "text" || type === "textarea") return { ...base, type };
|
|
25
|
-
if (type === "number") return { ...base, type };
|
|
25
|
+
if (type === "text" || type === "textarea" || type === "number" || type === "checkbox") return { ...base, type };
|
|
26
26
|
if (type === "rating") return { ...base, type, min: 1, max: 5 };
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
return { ...base, type, options: [] };
|
|
28
|
+
}
|
|
29
|
+
function defaultCreateOption(field, id) {
|
|
30
|
+
return { id, label: `Option ${"options" in field ? field.options.length + 1 : 1}` };
|
|
31
|
+
}
|
|
32
|
+
function defaultCreatePage(id, questionIds) {
|
|
33
|
+
return { id, title: "New page", questionIds };
|
|
29
34
|
}
|
|
30
35
|
function move(items, sourceIndex, targetIndex) {
|
|
31
36
|
if (sourceIndex < 0 || targetIndex < 0 || targetIndex >= items.length || sourceIndex === targetIndex)
|
|
@@ -36,334 +41,553 @@ function move(items, sourceIndex, targetIndex) {
|
|
|
36
41
|
result.splice(targetIndex, 0, item);
|
|
37
42
|
return result;
|
|
38
43
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
["title", field.title],
|
|
58
|
-
["description", field.description]
|
|
59
|
-
]) {
|
|
60
|
-
if (text !== void 0 && text.length > policy.maxTextLength) {
|
|
61
|
-
issues.push({
|
|
62
|
-
path: `fields[${index}].${property}`,
|
|
63
|
-
code: "max_text_length_exceeded",
|
|
64
|
-
message: `Text must be at most ${policy.maxTextLength} characters.`
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (policy?.maxOptionsPerField !== void 0 && "options" in field && field.options.length > policy.maxOptionsPerField) {
|
|
70
|
-
issues.push({
|
|
71
|
-
path: `fields[${index}].options`,
|
|
72
|
-
code: "max_options_exceeded",
|
|
73
|
-
message: `At most ${policy.maxOptionsPerField} options are allowed.`
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
for (const locale of policy?.requiredLocales ?? []) {
|
|
78
|
-
if (!(schema.supportedLocales ?? []).includes(locale)) {
|
|
79
|
-
issues.push({
|
|
80
|
-
path: "supportedLocales",
|
|
81
|
-
code: "required_locale_missing",
|
|
82
|
-
message: `Required locale ${locale} is missing.`
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
if (locale === schema.defaultLocale) continue;
|
|
86
|
-
const requiredTranslations = [
|
|
87
|
-
{ path: `translations.${locale}.title`, value: schema.translations?.[locale]?.title },
|
|
88
|
-
...schema.fields.map((field, index) => ({
|
|
89
|
-
path: `fields[${index}].translations.${locale}.title`,
|
|
90
|
-
value: field.translations?.[locale]?.title
|
|
91
|
-
})),
|
|
92
|
-
...schema.fields.flatMap(
|
|
93
|
-
(field, fieldIndex) => "options" in field ? field.options.map((option, optionIndex) => ({
|
|
94
|
-
path: `fields[${fieldIndex}].options[${optionIndex}].translations.${locale}`,
|
|
95
|
-
value: option.translations?.[locale]
|
|
96
|
-
})) : []
|
|
97
|
-
),
|
|
98
|
-
...schema.pages?.flatMap(
|
|
99
|
-
(page, pageIndex) => page.title === void 0 ? [] : [{ path: `pages[${pageIndex}].translations.${locale}.title`, value: page.translations?.[locale]?.title }]
|
|
100
|
-
) ?? []
|
|
101
|
-
];
|
|
102
|
-
for (const translation of requiredTranslations) {
|
|
103
|
-
if (translation.value === void 0 || translation.value.trim().length === 0) {
|
|
104
|
-
issues.push({
|
|
105
|
-
path: translation.path,
|
|
106
|
-
code: "required_translation_missing",
|
|
107
|
-
message: `A translation for required locale ${locale} is missing.`
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
44
|
+
function withoutDisplayCondition(field) {
|
|
45
|
+
const { displayCondition: _displayCondition, ...rest } = field;
|
|
46
|
+
return rest;
|
|
47
|
+
}
|
|
48
|
+
function removeLocalizedProperty(translations, locale, property) {
|
|
49
|
+
const current = translations?.[locale];
|
|
50
|
+
if (current === void 0) return translations;
|
|
51
|
+
const { [property]: _removed, ...remaining } = current;
|
|
52
|
+
return { ...translations, [locale]: remaining };
|
|
53
|
+
}
|
|
54
|
+
function setTranslationMetadata(node, locale, property, metadata, remove) {
|
|
55
|
+
const localeMetadata = node.translationMetadata?.[locale];
|
|
56
|
+
if (metadata === void 0 && !remove) return node;
|
|
57
|
+
const nextLocaleMetadata = remove ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== property)) : { ...localeMetadata, [property]: metadata ?? {} };
|
|
58
|
+
return { ...node, translationMetadata: { ...node.translationMetadata, [locale]: nextLocaleMetadata } };
|
|
59
|
+
}
|
|
60
|
+
function failedId(kind, result) {
|
|
61
|
+
return { success: false, error: result.error ?? { type: "invalid_id", kind, id: "" } };
|
|
112
62
|
}
|
|
113
63
|
function useFormBuilder({
|
|
114
64
|
schema,
|
|
115
65
|
onChange,
|
|
116
66
|
policy,
|
|
117
|
-
idFactory = defaultIdFactory
|
|
67
|
+
idFactory = defaultIdFactory,
|
|
68
|
+
factories = {}
|
|
118
69
|
}) {
|
|
119
70
|
const createId = useCallback(
|
|
120
71
|
(kind, existingIds) => {
|
|
121
|
-
const
|
|
122
|
-
|
|
72
|
+
const rawId = idFactory(kind, existingIds);
|
|
73
|
+
const id = rawId.trim();
|
|
74
|
+
return id.length > 0 && !existingIds.has(id) ? { id } : { error: { type: "invalid_id", kind, id: rawId } };
|
|
123
75
|
},
|
|
124
76
|
[idFactory]
|
|
125
77
|
);
|
|
78
|
+
const textPolicyError = useCallback(
|
|
79
|
+
(text) => policy?.maxTextLength !== void 0 && text.length > policy.maxTextLength ? { success: false, error: { type: "max_text_length_exceeded", max: policy.maxTextLength } } : void 0,
|
|
80
|
+
[policy?.maxTextLength]
|
|
81
|
+
);
|
|
82
|
+
const updateField = useCallback(
|
|
83
|
+
(fieldId, updater) => {
|
|
84
|
+
const current = schema.fields.find((field) => field.id === fieldId);
|
|
85
|
+
if (current === void 0)
|
|
86
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
87
|
+
const updated = updater(current);
|
|
88
|
+
if (updated.id !== fieldId)
|
|
89
|
+
return { success: false, error: { type: "invalid_id", kind: "field", id: updated.id } };
|
|
90
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type))
|
|
91
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: updated.type } };
|
|
92
|
+
for (const text of [updated.title, updated.description]) {
|
|
93
|
+
if (text !== void 0) {
|
|
94
|
+
const error = textPolicyError(text);
|
|
95
|
+
if (error !== void 0) return error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
99
|
+
return { success: true };
|
|
100
|
+
},
|
|
101
|
+
[onChange, policy?.allowedFieldTypes, schema, textPolicyError]
|
|
102
|
+
);
|
|
103
|
+
const updateOption = useCallback(
|
|
104
|
+
(fieldId, optionId, updater) => {
|
|
105
|
+
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
106
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
107
|
+
if (!("options" in field))
|
|
108
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
109
|
+
const current = field.options.find((option) => option.id === optionId);
|
|
110
|
+
if (current === void 0)
|
|
111
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
112
|
+
const updated = updater(current);
|
|
113
|
+
if (updated.id !== optionId)
|
|
114
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: updated.id } };
|
|
115
|
+
const error = textPolicyError(updated.label);
|
|
116
|
+
if (error !== void 0) return error;
|
|
117
|
+
onChange({
|
|
118
|
+
...schema,
|
|
119
|
+
fields: schema.fields.map(
|
|
120
|
+
(candidate) => candidate.id === fieldId && "options" in candidate ? {
|
|
121
|
+
...candidate,
|
|
122
|
+
options: candidate.options.map((option) => option.id === optionId ? updated : option)
|
|
123
|
+
} : candidate
|
|
124
|
+
)
|
|
125
|
+
});
|
|
126
|
+
return { success: true };
|
|
127
|
+
},
|
|
128
|
+
[onChange, schema, textPolicyError]
|
|
129
|
+
);
|
|
130
|
+
const updatePage = useCallback(
|
|
131
|
+
(pageId, updater) => {
|
|
132
|
+
const page = schema.pages?.find((candidate) => candidate.id === pageId);
|
|
133
|
+
if (page === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
134
|
+
const pages = schema.pages;
|
|
135
|
+
if (pages === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
136
|
+
const updated = updater(page);
|
|
137
|
+
if (updated.id !== pageId) return { success: false, error: { type: "invalid_id", kind: "page", id: updated.id } };
|
|
138
|
+
onChange({ ...schema, pages: pages.map((candidate) => candidate.id === pageId ? updated : candidate) });
|
|
139
|
+
return { success: true };
|
|
140
|
+
},
|
|
141
|
+
[onChange, schema]
|
|
142
|
+
);
|
|
126
143
|
const addField = useCallback(
|
|
127
144
|
(type, pageId) => {
|
|
128
|
-
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
145
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
129
146
|
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
130
|
-
|
|
131
|
-
if (policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields) {
|
|
147
|
+
if (policy?.maxFields !== void 0 && schema.fields.length >= policy.maxFields)
|
|
132
148
|
return { success: false, error: { type: "max_fields_exceeded", max: policy.maxFields } };
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (pageId !== void 0 && !schema.pages?.some((page) => page.id === pageId))
|
|
150
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
151
|
+
const fieldIds = new Set(schema.fields.map((field2) => field2.id));
|
|
152
|
+
const fieldId = createId("field", fieldIds);
|
|
153
|
+
if (fieldId.id === void 0) return failedId("field", fieldId);
|
|
154
|
+
let field = (factories.createField ?? defaultCreateField)(type, fieldId.id);
|
|
155
|
+
if (field.id !== fieldId.id || field.type !== type || fieldIds.has(field.id))
|
|
156
|
+
return { success: false, error: { type: "invalid_id", kind: "field", id: field.id } };
|
|
157
|
+
if (CHOICE_TYPES.includes(type) && "options" in field && field.options.length === 0) {
|
|
158
|
+
const optionIds = new Set(
|
|
159
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
160
|
+
);
|
|
161
|
+
const optionId = createId("option", optionIds);
|
|
162
|
+
if (optionId.id === void 0) return failedId("option", optionId);
|
|
163
|
+
const option = (factories.createOption ?? defaultCreateOption)(field, optionId.id);
|
|
164
|
+
if (option.id !== optionId.id || optionIds.has(option.id))
|
|
165
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
166
|
+
field = { ...field, options: [option] };
|
|
151
167
|
}
|
|
152
168
|
const pages = schema.pages?.map((page, index) => ({
|
|
153
169
|
...page,
|
|
154
|
-
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds,
|
|
170
|
+
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, field.id] : page.questionIds
|
|
155
171
|
}));
|
|
156
|
-
if (schema.pages !== void 0 && !schema.pages.some((page) => page.id === pageId) && pageId !== void 0) {
|
|
157
|
-
return { success: false, error: { type: "invalid_operation", message: `Unknown page: ${pageId}` } };
|
|
158
|
-
}
|
|
159
172
|
onChange({ ...schema, fields: [...schema.fields, field], ...pages === void 0 ? {} : { pages } });
|
|
160
173
|
return { success: true };
|
|
161
174
|
},
|
|
162
|
-
[createId, onChange, policy, schema]
|
|
175
|
+
[createId, factories, onChange, policy, schema]
|
|
163
176
|
);
|
|
164
177
|
const removeField = useCallback(
|
|
165
178
|
(fieldId) => {
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
179
|
+
if (!schema.fields.some((field) => field.id === fieldId))
|
|
180
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
181
|
+
if (schema.fields.length <= 1)
|
|
182
|
+
return { success: false, error: { type: "invalid_operation", message: "A form must contain one field." } };
|
|
183
|
+
const fields = schema.fields.filter((field) => field.id !== fieldId).map((field) => field.displayCondition?.questionId === fieldId ? withoutDisplayCondition(field) : field);
|
|
184
|
+
const pages = schema.pages?.map((page) => ({ ...page, questionIds: page.questionIds.filter((id) => id !== fieldId) })).filter((page) => page.questionIds.length > 0);
|
|
185
|
+
if (schema.pages !== void 0 && pages?.length === 0) {
|
|
186
|
+
const { pages: _pages, ...single } = schema;
|
|
187
|
+
onChange({ ...single, fields });
|
|
188
|
+
} else onChange({ ...schema, fields, ...pages === void 0 ? {} : { pages } });
|
|
189
|
+
return { success: true };
|
|
177
190
|
},
|
|
178
191
|
[onChange, schema]
|
|
179
192
|
);
|
|
180
193
|
const moveField = useCallback(
|
|
181
194
|
(fieldId, targetIndex) => {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
195
|
+
const sourceIndex = schema.fields.findIndex((field) => field.id === fieldId);
|
|
196
|
+
if (sourceIndex < 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
197
|
+
const fields = move(schema.fields, sourceIndex, targetIndex);
|
|
198
|
+
if (fields === void 0)
|
|
199
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid field position." } };
|
|
200
|
+
const indexById = new Map(fields.map((field, index) => [field.id, index]));
|
|
201
|
+
onChange({
|
|
202
|
+
...schema,
|
|
203
|
+
fields: fields.map((field, index) => {
|
|
204
|
+
const source = field.displayCondition?.questionId;
|
|
205
|
+
return source === void 0 || (indexById.get(source) ?? index) < index ? field : withoutDisplayCondition(field);
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
return { success: true };
|
|
197
209
|
},
|
|
198
210
|
[onChange, schema]
|
|
199
211
|
);
|
|
200
|
-
const updateField = useCallback(
|
|
201
|
-
(fieldId, updater) => {
|
|
202
|
-
const current = schema.fields.find((field) => field.id === fieldId);
|
|
203
|
-
if (current === void 0) return;
|
|
204
|
-
const updated = updater(current);
|
|
205
|
-
if (updated.id !== fieldId) return;
|
|
206
|
-
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type)) return;
|
|
207
|
-
const maxTextLength = policy?.maxTextLength;
|
|
208
|
-
if (maxTextLength !== void 0 && [updated.title, updated.description].some((text) => text !== void 0 && text.length > maxTextLength)) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
212
|
-
},
|
|
213
|
-
[onChange, policy, schema]
|
|
214
|
-
);
|
|
215
212
|
const addOption = useCallback(
|
|
216
213
|
(fieldId) => {
|
|
217
214
|
const field = schema.fields.find((candidate) => candidate.id === fieldId);
|
|
218
|
-
if (field === void 0
|
|
215
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
216
|
+
if (!("options" in field))
|
|
219
217
|
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
220
|
-
|
|
221
|
-
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField) {
|
|
218
|
+
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField)
|
|
222
219
|
return { success: false, error: { type: "max_options_exceeded", max: policy.maxOptionsPerField } };
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
schema.fields.flatMap(
|
|
226
|
-
(candidate) => "options" in candidate ? candidate.options.map((option2) => option2.id) : []
|
|
227
|
-
)
|
|
220
|
+
const ids = new Set(
|
|
221
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
228
222
|
);
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const option = { id: optionId, label: `Option ${field.options.length + 1}` };
|
|
223
|
+
const id = createId("option", ids);
|
|
224
|
+
if (id.id === void 0) return failedId("option", id);
|
|
225
|
+
const option = (factories.createOption ?? defaultCreateOption)(field, id.id);
|
|
226
|
+
if (option.id !== id.id || ids.has(option.id))
|
|
227
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
237
228
|
onChange({
|
|
238
229
|
...schema,
|
|
239
230
|
fields: schema.fields.map(
|
|
240
|
-
(
|
|
231
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options: [...item.options, option] } : item
|
|
241
232
|
)
|
|
242
233
|
});
|
|
243
234
|
return { success: true };
|
|
244
235
|
},
|
|
245
|
-
[createId, onChange, policy, schema]
|
|
236
|
+
[createId, factories.createOption, onChange, policy?.maxOptionsPerField, schema]
|
|
246
237
|
);
|
|
247
238
|
const removeOption = useCallback(
|
|
248
239
|
(fieldId, optionId) => {
|
|
249
|
-
const field = schema.fields.find((
|
|
250
|
-
if (field === void 0
|
|
240
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
241
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
242
|
+
if (!("options" in field) || !field.options.some((option) => option.id === optionId))
|
|
243
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
244
|
+
if (field.options.length <= 1)
|
|
245
|
+
return { success: false, error: { type: "invalid_operation", message: "A choice field needs one option." } };
|
|
251
246
|
onChange({
|
|
252
247
|
...schema,
|
|
253
248
|
fields: schema.fields.map(
|
|
254
|
-
(
|
|
249
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options: item.options.filter((option) => option.id !== optionId) } : item
|
|
255
250
|
)
|
|
256
251
|
});
|
|
252
|
+
return { success: true };
|
|
257
253
|
},
|
|
258
254
|
[onChange, schema]
|
|
259
255
|
);
|
|
260
256
|
const moveOption = useCallback(
|
|
261
257
|
(fieldId, optionId, targetIndex) => {
|
|
262
|
-
const field = schema.fields.find((
|
|
263
|
-
if (field === void 0
|
|
258
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
259
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
260
|
+
if (!("options" in field))
|
|
261
|
+
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
264
262
|
const options = move(
|
|
265
263
|
field.options,
|
|
266
264
|
field.options.findIndex((option) => option.id === optionId),
|
|
267
265
|
targetIndex
|
|
268
266
|
);
|
|
269
|
-
if (options === void 0)
|
|
267
|
+
if (options === void 0)
|
|
268
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid option position." } };
|
|
270
269
|
onChange({
|
|
271
270
|
...schema,
|
|
272
271
|
fields: schema.fields.map(
|
|
273
|
-
(
|
|
272
|
+
(item) => item.id === fieldId && "options" in item ? { ...item, options } : item
|
|
274
273
|
)
|
|
275
274
|
});
|
|
275
|
+
return { success: true };
|
|
276
276
|
},
|
|
277
277
|
[onChange, schema]
|
|
278
278
|
);
|
|
279
|
+
const changeFieldType = useCallback(
|
|
280
|
+
(fieldId, type) => {
|
|
281
|
+
const field = schema.fields.find((item) => item.id === fieldId);
|
|
282
|
+
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
283
|
+
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
284
|
+
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
285
|
+
let transformed = transformFieldType(field, type);
|
|
286
|
+
if (CHOICE_TYPES.includes(type) && !("options" in field) && "options" in transformed) {
|
|
287
|
+
const ids = new Set(
|
|
288
|
+
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
289
|
+
);
|
|
290
|
+
const id = createId("option", ids);
|
|
291
|
+
if (id.id === void 0) return failedId("option", id);
|
|
292
|
+
const option = (factories.createOption ?? defaultCreateOption)(transformed, id.id);
|
|
293
|
+
if (option.id !== id.id || ids.has(option.id))
|
|
294
|
+
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
295
|
+
transformed = { ...transformed, options: [option] };
|
|
296
|
+
}
|
|
297
|
+
onChange({ ...schema, fields: schema.fields.map((item) => item.id === fieldId ? transformed : item) });
|
|
298
|
+
return { success: true };
|
|
299
|
+
},
|
|
300
|
+
[createId, factories.createOption, onChange, policy?.allowedFieldTypes, schema]
|
|
301
|
+
);
|
|
279
302
|
const addPage = useCallback(
|
|
280
303
|
(questionId) => {
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
const ids = new Set(schema.pages?.map((page2) => page2.id) ?? []);
|
|
305
|
+
const id = createId("page", ids);
|
|
306
|
+
if (id.id === void 0) return failedId("page", id);
|
|
307
|
+
const questionIds = schema.pages === void 0 ? schema.fields.map((field) => field.id) : [questionId ?? schema.pages.find((page2) => page2.questionIds.length > 1)?.questionIds.at(-1)].filter(
|
|
308
|
+
(value) => value !== void 0
|
|
309
|
+
);
|
|
310
|
+
if (questionIds.length === 0)
|
|
311
|
+
return { success: false, error: { type: "invalid_operation", message: "No question can be moved." } };
|
|
312
|
+
const source = schema.pages?.find((page2) => page2.questionIds.includes(questionIds[0] ?? ""));
|
|
313
|
+
if (schema.pages !== void 0 && (source === void 0 || source.questionIds.length <= 1))
|
|
314
|
+
return { success: false, error: { type: "invalid_operation", message: "A page cannot be left empty." } };
|
|
315
|
+
const page = (factories.createPage ?? defaultCreatePage)(id.id, [...questionIds]);
|
|
316
|
+
if (page.id !== id.id || ids.has(page.id))
|
|
317
|
+
return { success: false, error: { type: "invalid_id", kind: "page", id: page.id } };
|
|
318
|
+
const pages = schema.pages === void 0 ? [page] : [
|
|
319
|
+
...schema.pages.map((item) => ({
|
|
320
|
+
...item,
|
|
321
|
+
questionIds: item.questionIds.filter((fieldId) => !questionIds.includes(fieldId))
|
|
296
322
|
})),
|
|
297
|
-
|
|
323
|
+
page
|
|
298
324
|
];
|
|
299
325
|
onChange({ ...schema, pages });
|
|
326
|
+
return { success: true };
|
|
300
327
|
},
|
|
301
|
-
[createId, onChange, schema]
|
|
328
|
+
[createId, factories.createPage, onChange, schema]
|
|
302
329
|
);
|
|
303
330
|
const removePage = useCallback(
|
|
304
331
|
(pageId) => {
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
332
|
+
const index = schema.pages?.findIndex((page) => page.id === pageId) ?? -1;
|
|
333
|
+
const removed = schema.pages?.[index];
|
|
334
|
+
if (removed === void 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
335
|
+
const currentPages = schema.pages;
|
|
336
|
+
if (currentPages === void 0)
|
|
337
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
338
|
+
if ((schema.pages?.length ?? 0) === 1) {
|
|
339
|
+
const { pages: _pages, ...single } = schema;
|
|
340
|
+
onChange(single);
|
|
341
|
+
return { success: true };
|
|
313
342
|
}
|
|
314
343
|
const targetIndex = index === 0 ? 1 : index - 1;
|
|
315
344
|
onChange({
|
|
316
345
|
...schema,
|
|
317
|
-
pages:
|
|
346
|
+
pages: currentPages.map(
|
|
318
347
|
(page, pageIndex) => pageIndex === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
319
348
|
).filter((page) => page.id !== pageId)
|
|
320
349
|
});
|
|
350
|
+
return { success: true };
|
|
321
351
|
},
|
|
322
352
|
[onChange, schema]
|
|
323
353
|
);
|
|
324
|
-
const
|
|
325
|
-
(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
354
|
+
const movePage = useCallback(
|
|
355
|
+
(pageId, targetIndex) => {
|
|
356
|
+
const sourceIndex = schema.pages?.findIndex((page) => page.id === pageId) ?? -1;
|
|
357
|
+
if (sourceIndex < 0) return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
358
|
+
const pages = move(schema.pages ?? [], sourceIndex, targetIndex);
|
|
359
|
+
if (pages === void 0)
|
|
360
|
+
return { success: false, error: { type: "invalid_operation", message: "Invalid page position." } };
|
|
361
|
+
const assignment = new Map(pages.flatMap((page, index) => page.questionIds.map((id) => [id, index])));
|
|
362
|
+
onChange({
|
|
363
|
+
...schema,
|
|
364
|
+
pages: pages.map((page, index) => {
|
|
365
|
+
const source = page.displayCondition?.questionId;
|
|
366
|
+
if (source === void 0 || (assignment.get(source) ?? index) < index) return page;
|
|
367
|
+
const { displayCondition: _condition, ...rest } = page;
|
|
368
|
+
return rest;
|
|
369
|
+
})
|
|
370
|
+
});
|
|
371
|
+
return { success: true };
|
|
372
|
+
},
|
|
373
|
+
[onChange, schema]
|
|
374
|
+
);
|
|
375
|
+
const assignFieldToPage = useCallback(
|
|
376
|
+
(fieldId, pageId) => {
|
|
377
|
+
if (!schema.fields.some((field) => field.id === fieldId))
|
|
378
|
+
return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
379
|
+
if (pageId === null) {
|
|
380
|
+
if (schema.pages !== void 0) {
|
|
381
|
+
const { pages: _pages, ...single } = schema;
|
|
382
|
+
onChange(single);
|
|
383
|
+
}
|
|
384
|
+
return { success: true };
|
|
385
|
+
}
|
|
386
|
+
if (!schema.pages?.some((page) => page.id === pageId))
|
|
387
|
+
return { success: false, error: { type: "node_not_found", kind: "page", id: pageId } };
|
|
388
|
+
const pages = schema.pages.map((page) => ({
|
|
389
|
+
...page,
|
|
390
|
+
questionIds: page.id === pageId ? schema.fields.filter((field) => page.questionIds.includes(field.id) || field.id === fieldId).map((field) => field.id) : page.questionIds.filter((id) => id !== fieldId)
|
|
391
|
+
})).filter((page) => page.questionIds.length > 0);
|
|
392
|
+
onChange({ ...schema, pages });
|
|
393
|
+
return { success: true };
|
|
394
|
+
},
|
|
395
|
+
[onChange, schema]
|
|
396
|
+
);
|
|
397
|
+
const setDisplayCondition = useCallback(
|
|
398
|
+
(fieldId, condition) => {
|
|
399
|
+
const index = schema.fields.findIndex((field) => field.id === fieldId);
|
|
400
|
+
if (index < 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
401
|
+
if (condition !== void 0) {
|
|
402
|
+
const sourceIndex = schema.fields.findIndex((field) => field.id === condition.questionId);
|
|
403
|
+
if (sourceIndex < 0 || sourceIndex >= index)
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
error: { type: "invalid_operation", message: "Conditions require an earlier field." }
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
onChange({
|
|
410
|
+
...schema,
|
|
411
|
+
fields: schema.fields.map(
|
|
412
|
+
(field) => field.id !== fieldId ? field : condition === void 0 ? withoutDisplayCondition(field) : { ...field, displayCondition: condition }
|
|
413
|
+
)
|
|
414
|
+
});
|
|
415
|
+
return { success: true };
|
|
416
|
+
},
|
|
417
|
+
[onChange, schema]
|
|
418
|
+
);
|
|
419
|
+
const setSourceText = useCallback(
|
|
420
|
+
(target, property, text) => {
|
|
421
|
+
const error = textPolicyError(text);
|
|
422
|
+
if (error !== void 0) return error;
|
|
423
|
+
if (target.kind === "form") {
|
|
424
|
+
if (!["title", "description", "completionMessage"].includes(property))
|
|
425
|
+
return {
|
|
426
|
+
success: false,
|
|
427
|
+
error: { type: "invalid_operation", message: `Unsupported form property: ${property}` }
|
|
428
|
+
};
|
|
429
|
+
if (property === "title") onChange({ ...schema, title: text });
|
|
430
|
+
else if (property === "description") {
|
|
431
|
+
const { description: _description, ...withoutDescription } = schema;
|
|
432
|
+
onChange(text.length === 0 ? withoutDescription : { ...schema, description: text });
|
|
433
|
+
} else {
|
|
434
|
+
const { completionMessage: _completionMessage, ...withoutCompletionMessage } = schema;
|
|
435
|
+
onChange(text.length === 0 ? withoutCompletionMessage : { ...schema, completionMessage: text });
|
|
436
|
+
}
|
|
437
|
+
return { success: true };
|
|
438
|
+
}
|
|
439
|
+
if (target.id === void 0)
|
|
440
|
+
return { success: false, error: { type: "invalid_operation", message: "A target ID is required." } };
|
|
441
|
+
if (target.kind === "field") {
|
|
442
|
+
if (!["title", "description"].includes(property))
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: { type: "invalid_operation", message: `Unsupported field property: ${property}` }
|
|
446
|
+
};
|
|
447
|
+
return updateField(target.id, (field) => {
|
|
448
|
+
if (property === "title") return { ...field, title: text };
|
|
449
|
+
const { description: _description, ...withoutDescription } = field;
|
|
450
|
+
return text.length === 0 ? withoutDescription : { ...field, description: text };
|
|
334
451
|
});
|
|
335
|
-
return;
|
|
336
452
|
}
|
|
337
|
-
|
|
338
|
-
if (
|
|
453
|
+
if (target.kind === "option") {
|
|
454
|
+
if (property !== "label")
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: { type: "invalid_operation", message: `Unsupported option property: ${property}` }
|
|
458
|
+
};
|
|
459
|
+
for (const field of schema.fields)
|
|
460
|
+
if ("options" in field && field.options.some((option) => option.id === target.id))
|
|
461
|
+
return updateOption(field.id, target.id, (option) => ({ ...option, label: text }));
|
|
462
|
+
return { success: false, error: { type: "node_not_found", kind: "option", id: target.id } };
|
|
463
|
+
}
|
|
464
|
+
if (!["title", "description"].includes(property))
|
|
465
|
+
return {
|
|
466
|
+
success: false,
|
|
467
|
+
error: { type: "invalid_operation", message: `Unsupported page property: ${property}` }
|
|
468
|
+
};
|
|
469
|
+
return updatePage(target.id, (page) => {
|
|
470
|
+
if (property === "title") {
|
|
471
|
+
const { title: _title, ...withoutTitle } = page;
|
|
472
|
+
return text.length === 0 ? withoutTitle : { ...page, title: text };
|
|
473
|
+
}
|
|
474
|
+
const { description: _description, ...withoutDescription } = page;
|
|
475
|
+
return text.length === 0 ? withoutDescription : { ...page, description: text };
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
[onChange, schema, textPolicyError, updateField, updateOption, updatePage]
|
|
479
|
+
);
|
|
480
|
+
const setLocaleTranslation = useCallback(
|
|
481
|
+
(locale, target, property, text, options = {}) => {
|
|
482
|
+
const normalized = locale.trim();
|
|
483
|
+
if (normalized.length === 0)
|
|
484
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
485
|
+
const error = textPolicyError(text);
|
|
486
|
+
if (error !== void 0) return error;
|
|
487
|
+
const supportedLocales = [.../* @__PURE__ */ new Set([...schema.supportedLocales ?? [], normalized])];
|
|
488
|
+
const remove = text.length === 0;
|
|
489
|
+
if (target.kind === "form") {
|
|
490
|
+
if (!["title", "description", "completionMessage"].includes(property))
|
|
339
491
|
return {
|
|
340
|
-
|
|
341
|
-
|
|
492
|
+
success: false,
|
|
493
|
+
error: { type: "invalid_operation", message: `Unsupported form property: ${property}` }
|
|
342
494
|
};
|
|
495
|
+
const key = property;
|
|
496
|
+
const translations = remove ? removeLocalizedProperty(schema.translations, normalized, key) : { ...schema.translations, [normalized]: { ...schema.translations?.[normalized], [key]: text } };
|
|
497
|
+
onChange(
|
|
498
|
+
setTranslationMetadata(
|
|
499
|
+
{ ...schema, supportedLocales, ...translations === void 0 ? {} : { translations } },
|
|
500
|
+
normalized,
|
|
501
|
+
property,
|
|
502
|
+
options.metadata,
|
|
503
|
+
remove
|
|
504
|
+
)
|
|
505
|
+
);
|
|
506
|
+
return { success: true };
|
|
507
|
+
}
|
|
508
|
+
if (target.id === void 0)
|
|
509
|
+
return { success: false, error: { type: "invalid_operation", message: "A target ID is required." } };
|
|
510
|
+
let found = false;
|
|
511
|
+
const fields = schema.fields.map((field) => {
|
|
512
|
+
if (target.kind === "field" && field.id === target.id && ["title", "description"].includes(property)) {
|
|
513
|
+
found = true;
|
|
514
|
+
const key = property;
|
|
515
|
+
const translations = remove ? removeLocalizedProperty(field.translations, normalized, key) : { ...field.translations, [normalized]: { ...field.translations?.[normalized], [key]: text } };
|
|
516
|
+
return setTranslationMetadata(
|
|
517
|
+
{ ...field, ...translations === void 0 ? {} : { translations } },
|
|
518
|
+
normalized,
|
|
519
|
+
property,
|
|
520
|
+
options.metadata,
|
|
521
|
+
remove
|
|
522
|
+
);
|
|
343
523
|
}
|
|
344
|
-
if (
|
|
524
|
+
if (target.kind !== "option" || property !== "label" || !("options" in field)) return field;
|
|
345
525
|
return {
|
|
346
526
|
...field,
|
|
347
|
-
options: field.options.map(
|
|
348
|
-
(option
|
|
349
|
-
|
|
527
|
+
options: field.options.map((option) => {
|
|
528
|
+
if (option.id !== target.id) return option;
|
|
529
|
+
found = true;
|
|
530
|
+
const translations = remove ? Object.fromEntries(Object.entries(option.translations ?? {}).filter(([key]) => key !== normalized)) : { ...option.translations, [normalized]: text };
|
|
531
|
+
return setTranslationMetadata({ ...option, translations }, normalized, property, options.metadata, remove);
|
|
532
|
+
})
|
|
350
533
|
};
|
|
351
534
|
});
|
|
352
|
-
const pages = schema.pages?.map(
|
|
353
|
-
(page
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
535
|
+
const pages = schema.pages?.map((page) => {
|
|
536
|
+
if (target.kind !== "page" || page.id !== target.id || !["title", "description"].includes(property))
|
|
537
|
+
return page;
|
|
538
|
+
found = true;
|
|
539
|
+
const key = property;
|
|
540
|
+
const translations = remove ? removeLocalizedProperty(page.translations, normalized, key) : { ...page.translations, [normalized]: { ...page.translations?.[normalized], [key]: text } };
|
|
541
|
+
return setTranslationMetadata(
|
|
542
|
+
{ ...page, ...translations === void 0 ? {} : { translations } },
|
|
543
|
+
normalized,
|
|
544
|
+
property,
|
|
545
|
+
options.metadata,
|
|
546
|
+
remove
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
if (!found) return { success: false, error: { type: "node_not_found", kind: target.kind, id: target.id } };
|
|
358
550
|
onChange({ ...schema, supportedLocales, fields, ...pages === void 0 ? {} : { pages } });
|
|
551
|
+
return { success: true };
|
|
359
552
|
},
|
|
360
|
-
[onChange,
|
|
553
|
+
[onChange, schema, textPolicyError]
|
|
554
|
+
);
|
|
555
|
+
const addLocale = useCallback(
|
|
556
|
+
(locale) => {
|
|
557
|
+
const normalized = locale.trim();
|
|
558
|
+
if (normalized.length === 0)
|
|
559
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
560
|
+
onChange({
|
|
561
|
+
...schema,
|
|
562
|
+
supportedLocales: [
|
|
563
|
+
.../* @__PURE__ */ new Set([
|
|
564
|
+
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
565
|
+
...schema.supportedLocales ?? [],
|
|
566
|
+
normalized
|
|
567
|
+
])
|
|
568
|
+
]
|
|
569
|
+
});
|
|
570
|
+
return { success: true };
|
|
571
|
+
},
|
|
572
|
+
[onChange, schema]
|
|
573
|
+
);
|
|
574
|
+
const setDefaultLocale = useCallback(
|
|
575
|
+
(locale) => {
|
|
576
|
+
const normalized = locale.trim();
|
|
577
|
+
if (normalized.length === 0)
|
|
578
|
+
return { success: false, error: { type: "invalid_operation", message: "Locale must not be empty." } };
|
|
579
|
+
onChange({
|
|
580
|
+
...schema,
|
|
581
|
+
defaultLocale: normalized,
|
|
582
|
+
supportedLocales: [.../* @__PURE__ */ new Set([normalized, ...schema.supportedLocales ?? []])]
|
|
583
|
+
});
|
|
584
|
+
return { success: true };
|
|
585
|
+
},
|
|
586
|
+
[onChange, schema]
|
|
361
587
|
);
|
|
362
588
|
const validationIssues = useMemo(() => {
|
|
363
|
-
const result = validateFormSchema(schema);
|
|
364
|
-
|
|
365
|
-
addPolicyIssues(schema, policy, issues);
|
|
366
|
-
return issues;
|
|
589
|
+
const result = validateFormSchema(schema, policy === void 0 ? {} : { policy });
|
|
590
|
+
return result.valid ? [] : result.issues;
|
|
367
591
|
}, [policy, schema]);
|
|
368
592
|
return {
|
|
369
593
|
schema,
|
|
@@ -371,12 +595,21 @@ function useFormBuilder({
|
|
|
371
595
|
removeField,
|
|
372
596
|
moveField,
|
|
373
597
|
updateField,
|
|
598
|
+
changeFieldType,
|
|
374
599
|
addOption,
|
|
600
|
+
updateOption,
|
|
375
601
|
removeOption,
|
|
376
602
|
moveOption,
|
|
377
603
|
addPage,
|
|
604
|
+
updatePage,
|
|
378
605
|
removePage,
|
|
606
|
+
movePage,
|
|
607
|
+
assignFieldToPage,
|
|
608
|
+
setDisplayCondition,
|
|
609
|
+
setSourceText,
|
|
379
610
|
setLocaleTranslation,
|
|
611
|
+
addLocale,
|
|
612
|
+
setDefaultLocale,
|
|
380
613
|
validationIssues
|
|
381
614
|
};
|
|
382
615
|
}
|
|
@@ -402,6 +635,7 @@ var BUILDER_DEFAULTS = {
|
|
|
402
635
|
"builder.questionTitle": "\u8CEA\u554F\u6587 / Question Title",
|
|
403
636
|
"builder.questionTitlePlaceholder": "Example: Tell us what we could improve",
|
|
404
637
|
"builder.newQuestionTitle": "New question",
|
|
638
|
+
"builder.completionMessage": "Completion message",
|
|
405
639
|
"builder.type": "Type",
|
|
406
640
|
"builder.required": "Required",
|
|
407
641
|
"builder.minimum": "Minimum",
|
|
@@ -460,33 +694,6 @@ function fieldTypeKey(type) {
|
|
|
460
694
|
function operatorKey(operator) {
|
|
461
695
|
return `builder.operator.${operator}`;
|
|
462
696
|
}
|
|
463
|
-
function createUniqueId(prefix, existingIds) {
|
|
464
|
-
let id;
|
|
465
|
-
do {
|
|
466
|
-
id = `${prefix}_${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
467
|
-
} while (existingIds.has(id));
|
|
468
|
-
return id;
|
|
469
|
-
}
|
|
470
|
-
function baseField(field, type) {
|
|
471
|
-
return {
|
|
472
|
-
id: field.id,
|
|
473
|
-
type,
|
|
474
|
-
title: field.title,
|
|
475
|
-
...field.description === void 0 ? {} : { description: field.description },
|
|
476
|
-
...field.translationKey === void 0 ? {} : { translationKey: field.translationKey },
|
|
477
|
-
required: field.required,
|
|
478
|
-
...field.displayCondition === void 0 ? {} : { displayCondition: field.displayCondition }
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function normalizeField(field, type, newOptionLabel) {
|
|
482
|
-
const base = baseField(field, type);
|
|
483
|
-
if (type === "text" || type === "textarea") return { ...base, type };
|
|
484
|
-
if (type === "number") return { ...base, type };
|
|
485
|
-
if (type === "rating") return { ...base, type, min: 1, max: 5 };
|
|
486
|
-
if (type === "checkbox") return { ...base, type };
|
|
487
|
-
const options = "options" in field && field.options.length > 0 ? field.options : [{ id: createUniqueId("opt", /* @__PURE__ */ new Set()), label: newOptionLabel }];
|
|
488
|
-
return { ...base, type, options };
|
|
489
|
-
}
|
|
490
697
|
function defaultConditionValue(field) {
|
|
491
698
|
if (field.type === "checkbox") return true;
|
|
492
699
|
if (field.type === "number" || field.type === "rating") return field.min ?? 1;
|
|
@@ -500,30 +707,6 @@ function conditionOperators(field) {
|
|
|
500
707
|
}
|
|
501
708
|
return ["equals", "not_equals", "not_empty"];
|
|
502
709
|
}
|
|
503
|
-
function withoutDisplayCondition(field) {
|
|
504
|
-
const { displayCondition: _displayCondition, ...rest } = field;
|
|
505
|
-
return rest;
|
|
506
|
-
}
|
|
507
|
-
function sanitizeBuilderSchema(schema) {
|
|
508
|
-
const sanitized = sanitizeSchema(schema);
|
|
509
|
-
const indexById = new Map(sanitized.fields.map((field, index) => [field.id, index]));
|
|
510
|
-
const fields = sanitized.fields.map((field, index) => {
|
|
511
|
-
const sourceId = field.displayCondition?.questionId;
|
|
512
|
-
if (sourceId === void 0) return field;
|
|
513
|
-
const sourceIndex = indexById.get(sourceId);
|
|
514
|
-
return sourceIndex !== void 0 && sourceIndex < index ? field : withoutDisplayCondition(field);
|
|
515
|
-
});
|
|
516
|
-
return {
|
|
517
|
-
...sanitized,
|
|
518
|
-
fields,
|
|
519
|
-
...sanitized.pages === void 0 ? {} : {
|
|
520
|
-
pages: sanitized.pages.map((page) => ({
|
|
521
|
-
...page,
|
|
522
|
-
questionIds: fields.filter((field) => page.questionIds.includes(field.id)).map((field) => field.id)
|
|
523
|
-
}))
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
710
|
function conditionWithValue(questionId, operator, value) {
|
|
528
711
|
return operator === "not_empty" ? { questionId, operator } : { questionId, operator, value };
|
|
529
712
|
}
|
|
@@ -571,14 +754,18 @@ function FormBuilder({
|
|
|
571
754
|
locale = "en",
|
|
572
755
|
translator,
|
|
573
756
|
translationAdapter,
|
|
757
|
+
translationOptions,
|
|
758
|
+
onTranslationReport,
|
|
574
759
|
policy,
|
|
575
|
-
idFactory
|
|
760
|
+
idFactory,
|
|
761
|
+
factories
|
|
576
762
|
}) {
|
|
577
763
|
const headless = useFormBuilder({
|
|
578
764
|
schema,
|
|
579
765
|
onChange,
|
|
580
766
|
...policy === void 0 ? {} : { policy },
|
|
581
|
-
...idFactory === void 0 ? {} : { idFactory }
|
|
767
|
+
...idFactory === void 0 ? {} : { idFactory },
|
|
768
|
+
...factories === void 0 ? {} : { factories }
|
|
582
769
|
});
|
|
583
770
|
const [newPageQuestionId, setNewPageQuestionId] = useState("");
|
|
584
771
|
const [newLocale, setNewLocale] = useState("");
|
|
@@ -589,48 +776,17 @@ function FormBuilder({
|
|
|
589
776
|
const translated = translator?.translate(key, locale, params);
|
|
590
777
|
return translated === void 0 ? interpolate(BUILDER_DEFAULTS[key] ?? key, params) : translated;
|
|
591
778
|
};
|
|
592
|
-
const emitSchema = (candidate) => onChange(sanitizeBuilderSchema(candidate));
|
|
593
779
|
const updateField = headless.updateField;
|
|
594
|
-
const changeType =
|
|
595
|
-
emitSchema({
|
|
596
|
-
...schema,
|
|
597
|
-
fields: schema.fields.map((field) => {
|
|
598
|
-
if (field.id === fieldId) {
|
|
599
|
-
return normalizeField(field, type, translate("builder.newOptionLabel", { index: 1 }));
|
|
600
|
-
}
|
|
601
|
-
if (field.displayCondition?.questionId === fieldId) {
|
|
602
|
-
const { displayCondition: _condition, ...withoutCondition } = field;
|
|
603
|
-
return withoutCondition;
|
|
604
|
-
}
|
|
605
|
-
return field;
|
|
606
|
-
})
|
|
607
|
-
});
|
|
608
|
-
};
|
|
780
|
+
const changeType = headless.changeFieldType;
|
|
609
781
|
const removeField = headless.removeField;
|
|
610
782
|
const moveField = (index, offset) => {
|
|
611
783
|
const target = index + offset;
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const current = fields[index];
|
|
615
|
-
const other = fields[target];
|
|
616
|
-
if (current === void 0 || other === void 0) return;
|
|
617
|
-
fields[index] = other;
|
|
618
|
-
fields[target] = current;
|
|
619
|
-
emitSchema({ ...schema, fields });
|
|
784
|
+
const field = schema.fields[index];
|
|
785
|
+
if (field !== void 0) headless.moveField(field.id, target);
|
|
620
786
|
};
|
|
621
787
|
const addField = () => headless.addField("text");
|
|
622
788
|
const enablePages = () => {
|
|
623
|
-
if (schema.pages
|
|
624
|
-
emitSchema({
|
|
625
|
-
...schema,
|
|
626
|
-
pages: [
|
|
627
|
-
{
|
|
628
|
-
id: createUniqueId("page", /* @__PURE__ */ new Set()),
|
|
629
|
-
title: translate("builder.newPage"),
|
|
630
|
-
questionIds: schema.fields.map((field) => field.id)
|
|
631
|
-
}
|
|
632
|
-
]
|
|
633
|
-
});
|
|
789
|
+
if (schema.pages === void 0) headless.addPage();
|
|
634
790
|
};
|
|
635
791
|
const pageForField = (fieldId) => schema.pages?.find((page) => page.questionIds.includes(fieldId));
|
|
636
792
|
const movablePageQuestions = schema.pages?.flatMap((page) => page.questionIds.length > 1 ? page.questionIds : []) ?? [];
|
|
@@ -641,73 +797,28 @@ function FormBuilder({
|
|
|
641
797
|
}
|
|
642
798
|
const questionId = movablePageQuestions.includes(newPageQuestionId) ? newPageQuestionId : movablePageQuestions[0];
|
|
643
799
|
if (questionId === void 0) return;
|
|
644
|
-
|
|
645
|
-
emitSchema({
|
|
646
|
-
...schema,
|
|
647
|
-
pages: [
|
|
648
|
-
...schema.pages.map((page) => ({
|
|
649
|
-
...page,
|
|
650
|
-
questionIds: page.questionIds.filter((id) => id !== questionId)
|
|
651
|
-
})),
|
|
652
|
-
{ id: pageId, title: translate("builder.newPage"), questionIds: [questionId] }
|
|
653
|
-
]
|
|
654
|
-
});
|
|
800
|
+
headless.addPage(questionId);
|
|
655
801
|
setNewPageQuestionId("");
|
|
656
802
|
};
|
|
657
803
|
const removePage = (pageIndex) => {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (removed === void 0) return;
|
|
661
|
-
if (schema.pages.length === 1) {
|
|
662
|
-
const { pages: _pages, ...singlePage } = schema;
|
|
663
|
-
emitSchema(singlePage);
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const targetIndex = pageIndex === 0 ? 1 : pageIndex - 1;
|
|
667
|
-
emitSchema({
|
|
668
|
-
...schema,
|
|
669
|
-
pages: schema.pages.map(
|
|
670
|
-
(page, index) => index === targetIndex ? { ...page, questionIds: [...page.questionIds, ...removed.questionIds] } : page
|
|
671
|
-
).filter((_page, index) => index !== pageIndex)
|
|
672
|
-
});
|
|
804
|
+
const page = schema.pages?.[pageIndex];
|
|
805
|
+
if (page !== void 0) headless.removePage(page.id);
|
|
673
806
|
};
|
|
674
807
|
const movePage = (pageIndex, offset) => {
|
|
675
|
-
if (schema.pages === void 0) return;
|
|
676
808
|
const target = pageIndex + offset;
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const current = pages[pageIndex];
|
|
680
|
-
const other = pages[target];
|
|
681
|
-
if (current === void 0 || other === void 0) return;
|
|
682
|
-
pages[pageIndex] = other;
|
|
683
|
-
pages[target] = current;
|
|
684
|
-
emitSchema({ ...schema, pages });
|
|
809
|
+
const page = schema.pages?.[pageIndex];
|
|
810
|
+
if (page !== void 0) headless.movePage(page.id, target);
|
|
685
811
|
};
|
|
686
812
|
const updatePage = (pageId, update) => {
|
|
687
|
-
|
|
688
|
-
emitSchema({ ...schema, pages: schema.pages.map((page) => page.id === pageId ? update(page) : page) });
|
|
813
|
+
headless.updatePage(pageId, update);
|
|
689
814
|
};
|
|
690
815
|
const assignFieldToPage = (fieldId, pageId) => {
|
|
691
|
-
|
|
692
|
-
emitSchema({
|
|
693
|
-
...schema,
|
|
694
|
-
pages: schema.pages.map((page) => ({
|
|
695
|
-
...page,
|
|
696
|
-
questionIds: page.id === pageId ? schema.fields.filter((field) => page.questionIds.includes(field.id) || field.id === fieldId).map((field) => field.id) : page.questionIds.filter((id) => id !== fieldId)
|
|
697
|
-
})).filter((page) => page.questionIds.length > 0)
|
|
698
|
-
});
|
|
816
|
+
headless.assignFieldToPage(fieldId, pageId);
|
|
699
817
|
};
|
|
700
818
|
const addLocale = () => {
|
|
701
819
|
const normalized = newLocale.trim();
|
|
702
820
|
if (normalized.length === 0) return;
|
|
703
|
-
|
|
704
|
-
.../* @__PURE__ */ new Set([
|
|
705
|
-
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
706
|
-
...schema.supportedLocales ?? [],
|
|
707
|
-
normalized
|
|
708
|
-
])
|
|
709
|
-
];
|
|
710
|
-
emitSchema({ ...schema, supportedLocales });
|
|
821
|
+
headless.addLocale(normalized);
|
|
711
822
|
setEditingLocale(normalized);
|
|
712
823
|
setNewLocale("");
|
|
713
824
|
};
|
|
@@ -716,10 +827,14 @@ function FormBuilder({
|
|
|
716
827
|
setIsTranslating(true);
|
|
717
828
|
setTranslationError(null);
|
|
718
829
|
try {
|
|
719
|
-
const populated = await populateSchemaTranslations(
|
|
720
|
-
|
|
721
|
-
|
|
830
|
+
const populated = await populateSchemaTranslations(
|
|
831
|
+
schema,
|
|
832
|
+
[editingLocale],
|
|
833
|
+
translationAdapter,
|
|
834
|
+
translationOptions ?? { overwrite: "all" }
|
|
835
|
+
);
|
|
722
836
|
onChange(populated.schema);
|
|
837
|
+
onTranslationReport?.(populated.report);
|
|
723
838
|
} catch (cause) {
|
|
724
839
|
setTranslationError(cause instanceof Error ? cause.message : String(cause));
|
|
725
840
|
} finally {
|
|
@@ -728,18 +843,7 @@ function FormBuilder({
|
|
|
728
843
|
};
|
|
729
844
|
const updateFormTranslation = (key, value) => {
|
|
730
845
|
if (editingLocale.length === 0) return;
|
|
731
|
-
|
|
732
|
-
const next = key === "title" ? value.length === 0 ? { description: current?.description } : { ...current, title: value } : value.length === 0 ? { title: current?.title } : { ...current, description: value };
|
|
733
|
-
emitSchema({
|
|
734
|
-
...schema,
|
|
735
|
-
translations: {
|
|
736
|
-
...schema.translations,
|
|
737
|
-
[editingLocale]: {
|
|
738
|
-
...next.title === void 0 ? {} : { title: next.title },
|
|
739
|
-
...next.description === void 0 ? {} : { description: next.description }
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
});
|
|
846
|
+
headless.setLocaleTranslation(editingLocale, { kind: "form" }, key, value);
|
|
743
847
|
};
|
|
744
848
|
return /* @__PURE__ */ jsxs("section", { className: "form-engine-builder", "aria-label": translate("builder.formBuilder"), children: [
|
|
745
849
|
/* @__PURE__ */ jsxs("section", { className: "form-engine-builder__pages", "aria-labelledby": "builder-pages-heading", children: [
|
|
@@ -819,19 +923,12 @@ function FormBuilder({
|
|
|
819
923
|
"input",
|
|
820
924
|
{
|
|
821
925
|
value: page.translations?.[editingLocale]?.title ?? "",
|
|
822
|
-
onChange: (event) =>
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
[editingLocale]: {
|
|
829
|
-
...value.length === 0 ? {} : { title: value },
|
|
830
|
-
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}));
|
|
834
|
-
}
|
|
926
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
927
|
+
editingLocale,
|
|
928
|
+
{ kind: "page", id: page.id },
|
|
929
|
+
"title",
|
|
930
|
+
event.currentTarget.value
|
|
931
|
+
)
|
|
835
932
|
}
|
|
836
933
|
)
|
|
837
934
|
] }),
|
|
@@ -841,19 +938,12 @@ function FormBuilder({
|
|
|
841
938
|
"input",
|
|
842
939
|
{
|
|
843
940
|
value: page.translations?.[editingLocale]?.description ?? "",
|
|
844
|
-
onChange: (event) =>
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
[editingLocale]: {
|
|
851
|
-
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
852
|
-
...value.length === 0 ? {} : { description: value }
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}));
|
|
856
|
-
}
|
|
941
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
942
|
+
editingLocale,
|
|
943
|
+
{ kind: "page", id: page.id },
|
|
944
|
+
"description",
|
|
945
|
+
event.currentTarget.value
|
|
946
|
+
)
|
|
857
947
|
}
|
|
858
948
|
)
|
|
859
949
|
] })
|
|
@@ -941,6 +1031,16 @@ function FormBuilder({
|
|
|
941
1031
|
] }),
|
|
942
1032
|
/* @__PURE__ */ jsxs("section", { className: "form-engine-builder__localization", "aria-labelledby": "builder-localization-heading", children: [
|
|
943
1033
|
/* @__PURE__ */ jsx("h2", { id: "builder-localization-heading", children: translate("builder.localization") }),
|
|
1034
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
1035
|
+
translate("builder.completionMessage"),
|
|
1036
|
+
/* @__PURE__ */ jsx(
|
|
1037
|
+
"input",
|
|
1038
|
+
{
|
|
1039
|
+
value: schema.completionMessage ?? "",
|
|
1040
|
+
onChange: (event) => headless.setSourceText({ kind: "form" }, "completionMessage", event.currentTarget.value)
|
|
1041
|
+
}
|
|
1042
|
+
)
|
|
1043
|
+
] }),
|
|
944
1044
|
/* @__PURE__ */ jsxs("div", { className: "form-engine-builder__grid", children: [
|
|
945
1045
|
/* @__PURE__ */ jsxs("label", { children: [
|
|
946
1046
|
translate("builder.defaultLocale"),
|
|
@@ -948,16 +1048,7 @@ function FormBuilder({
|
|
|
948
1048
|
"input",
|
|
949
1049
|
{
|
|
950
1050
|
value: schema.defaultLocale ?? "",
|
|
951
|
-
onChange: (event) =>
|
|
952
|
-
const value = event.currentTarget.value.trim();
|
|
953
|
-
emitSchema(
|
|
954
|
-
value.length === 0 ? schema : {
|
|
955
|
-
...schema,
|
|
956
|
-
defaultLocale: value,
|
|
957
|
-
supportedLocales: [.../* @__PURE__ */ new Set([value, ...schema.supportedLocales ?? []])]
|
|
958
|
-
}
|
|
959
|
-
);
|
|
960
|
-
}
|
|
1051
|
+
onChange: (event) => headless.setDefaultLocale(event.currentTarget.value)
|
|
961
1052
|
}
|
|
962
1053
|
)
|
|
963
1054
|
] }),
|
|
@@ -1005,6 +1096,16 @@ function FormBuilder({
|
|
|
1005
1096
|
onChange: (event) => updateFormTranslation("description", event.currentTarget.value)
|
|
1006
1097
|
}
|
|
1007
1098
|
)
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
1101
|
+
translate("builder.completionMessage"),
|
|
1102
|
+
/* @__PURE__ */ jsx(
|
|
1103
|
+
"input",
|
|
1104
|
+
{
|
|
1105
|
+
value: schema.translations?.[editingLocale]?.completionMessage ?? "",
|
|
1106
|
+
onChange: (event) => updateFormTranslation("completionMessage", event.currentTarget.value)
|
|
1107
|
+
}
|
|
1108
|
+
)
|
|
1008
1109
|
] })
|
|
1009
1110
|
] })
|
|
1010
1111
|
] }),
|
|
@@ -1106,19 +1207,12 @@ function FormBuilder({
|
|
|
1106
1207
|
"input",
|
|
1107
1208
|
{
|
|
1108
1209
|
value: field.translations?.[editingLocale]?.title ?? "",
|
|
1109
|
-
onChange: (event) =>
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
[editingLocale]: {
|
|
1116
|
-
...value.length === 0 ? {} : { title: value },
|
|
1117
|
-
...current.translations?.[editingLocale]?.description === void 0 ? {} : { description: current.translations[editingLocale]?.description }
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}));
|
|
1121
|
-
}
|
|
1210
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1211
|
+
editingLocale,
|
|
1212
|
+
{ kind: "field", id: field.id },
|
|
1213
|
+
"title",
|
|
1214
|
+
event.currentTarget.value
|
|
1215
|
+
)
|
|
1122
1216
|
}
|
|
1123
1217
|
)
|
|
1124
1218
|
] }),
|
|
@@ -1128,19 +1222,12 @@ function FormBuilder({
|
|
|
1128
1222
|
"input",
|
|
1129
1223
|
{
|
|
1130
1224
|
value: field.translations?.[editingLocale]?.description ?? "",
|
|
1131
|
-
onChange: (event) =>
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
[editingLocale]: {
|
|
1138
|
-
...current.translations?.[editingLocale]?.title === void 0 ? {} : { title: current.translations[editingLocale]?.title },
|
|
1139
|
-
...value.length === 0 ? {} : { description: value }
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
}));
|
|
1143
|
-
}
|
|
1225
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1226
|
+
editingLocale,
|
|
1227
|
+
{ kind: "field", id: field.id },
|
|
1228
|
+
"description",
|
|
1229
|
+
event.currentTarget.value
|
|
1230
|
+
)
|
|
1144
1231
|
}
|
|
1145
1232
|
)
|
|
1146
1233
|
] })
|
|
@@ -1154,26 +1241,12 @@ function FormBuilder({
|
|
|
1154
1241
|
"input",
|
|
1155
1242
|
{
|
|
1156
1243
|
value: option.translations?.[editingLocale] ?? "",
|
|
1157
|
-
onChange: (event) =>
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
options: current.options.map(
|
|
1164
|
-
(candidate) => candidate.id === option.id ? {
|
|
1165
|
-
...candidate,
|
|
1166
|
-
translations: Object.fromEntries([
|
|
1167
|
-
...Object.entries(candidate.translations ?? {}).filter(
|
|
1168
|
-
([localeKey]) => localeKey !== editingLocale
|
|
1169
|
-
),
|
|
1170
|
-
...value.length === 0 ? [] : [[editingLocale, value]]
|
|
1171
|
-
])
|
|
1172
|
-
} : candidate
|
|
1173
|
-
)
|
|
1174
|
-
};
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1244
|
+
onChange: (event) => headless.setLocaleTranslation(
|
|
1245
|
+
editingLocale,
|
|
1246
|
+
{ kind: "option", id: option.id },
|
|
1247
|
+
"label",
|
|
1248
|
+
event.currentTarget.value
|
|
1249
|
+
)
|
|
1177
1250
|
}
|
|
1178
1251
|
)
|
|
1179
1252
|
] }, option.id)) : null
|
|
@@ -1225,17 +1298,10 @@ function FormBuilder({
|
|
|
1225
1298
|
"aria-label": translate("builder.optionLabel", { index: optionIndex + 1 }),
|
|
1226
1299
|
value: option.label,
|
|
1227
1300
|
placeholder: translate("builder.optionLabelPlaceholder"),
|
|
1228
|
-
onChange: (event) =>
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
return {
|
|
1233
|
-
...current,
|
|
1234
|
-
options: current.options.map(
|
|
1235
|
-
(item, itemIndex) => itemIndex === optionIndex ? { ...item, label } : item
|
|
1236
|
-
)
|
|
1237
|
-
};
|
|
1238
|
-
})
|
|
1301
|
+
onChange: (event) => event.currentTarget.value.trim().length === 0 ? void 0 : headless.updateOption(field.id, option.id, (item) => ({
|
|
1302
|
+
...item,
|
|
1303
|
+
label: event.currentTarget.value
|
|
1304
|
+
}))
|
|
1239
1305
|
}
|
|
1240
1306
|
),
|
|
1241
1307
|
/* @__PURE__ */ jsx(
|
|
@@ -1267,17 +1333,14 @@ function FormBuilder({
|
|
|
1267
1333
|
value: condition?.questionId ?? "",
|
|
1268
1334
|
onChange: (event) => {
|
|
1269
1335
|
const selected = schema.fields.find((item) => item.id === event.currentTarget.value);
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
displayCondition: conditionWithValue(selected.id, operator, defaultConditionValue(selected))
|
|
1279
|
-
};
|
|
1280
|
-
});
|
|
1336
|
+
headless.setDisplayCondition(
|
|
1337
|
+
field.id,
|
|
1338
|
+
selected === void 0 ? void 0 : conditionWithValue(
|
|
1339
|
+
selected.id,
|
|
1340
|
+
conditionOperators(selected)[0] ?? "not_empty",
|
|
1341
|
+
defaultConditionValue(selected)
|
|
1342
|
+
)
|
|
1343
|
+
);
|
|
1281
1344
|
},
|
|
1282
1345
|
children: [
|
|
1283
1346
|
/* @__PURE__ */ jsx("option", { value: "", children: translate("builder.alwaysVisible") }),
|
|
@@ -1294,10 +1357,10 @@ function FormBuilder({
|
|
|
1294
1357
|
value: condition.operator,
|
|
1295
1358
|
onChange: (event) => {
|
|
1296
1359
|
const operator = event.currentTarget.value;
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1360
|
+
headless.setDisplayCondition(
|
|
1361
|
+
field.id,
|
|
1362
|
+
conditionWithValue(source.id, operator, defaultConditionValue(source))
|
|
1363
|
+
);
|
|
1301
1364
|
},
|
|
1302
1365
|
children: conditionOperators(source).map((operator) => /* @__PURE__ */ jsx("option", { value: operator, children: translate(operatorKey(operator)) }, operator))
|
|
1303
1366
|
}
|
|
@@ -1307,7 +1370,7 @@ function FormBuilder({
|
|
|
1307
1370
|
{
|
|
1308
1371
|
source,
|
|
1309
1372
|
condition,
|
|
1310
|
-
onChange: (next) =>
|
|
1373
|
+
onChange: (next) => headless.setDisplayCondition(field.id, next),
|
|
1311
1374
|
translate
|
|
1312
1375
|
}
|
|
1313
1376
|
)
|
|
@@ -1859,8 +1922,14 @@ function ContextFormRenderer({
|
|
|
1859
1922
|
] }),
|
|
1860
1923
|
draftRestored ? /* @__PURE__ */ jsx3("span", { className: "form-draft-badge", children: form.translate("form.draftRestored") }) : null
|
|
1861
1924
|
] }),
|
|
1862
|
-
activePage
|
|
1863
|
-
|
|
1925
|
+
activePage === void 0 ? null : slots.renderPageHeader?.({
|
|
1926
|
+
page: activePage,
|
|
1927
|
+
pageIndex: activeVisibleIndex,
|
|
1928
|
+
totalPages: visiblePageIndexes.length
|
|
1929
|
+
}) ?? /* @__PURE__ */ jsxs2("div", { className: "fe-page-header", children: [
|
|
1930
|
+
activePage.title === void 0 ? null : /* @__PURE__ */ jsx3("h2", { className: "fe-page-title", children: activePage.title }),
|
|
1931
|
+
activePage.description === void 0 ? null : /* @__PURE__ */ jsx3("p", { className: "fe-page-description", children: activePage.description })
|
|
1932
|
+
] }),
|
|
1864
1933
|
/* @__PURE__ */ jsx3("div", { className: "fe-fields", children: form.schema.fields.filter((field) => form.visibility[field.id] === true && (fieldIds === void 0 || fieldIds.has(field.id))).map((field) => {
|
|
1865
1934
|
const error = form.errors[field.id];
|
|
1866
1935
|
const props = {
|
|
@@ -1928,7 +1997,7 @@ function ContextFormRenderer({
|
|
|
1928
1997
|
form.submitStatus === "success" ? slots.renderCompletion?.({
|
|
1929
1998
|
message: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey))
|
|
1930
1999
|
}) ?? /* @__PURE__ */ jsx3("div", { role: "status", children: form.schema.completionMessage ?? (successMessageKey === void 0 ? "Submitted." : form.translate(successMessageKey)) }) : null,
|
|
1931
|
-
form.submitStatus === "error" && form.submitError !== null
|
|
2000
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (errorMessageKey === void 0 ? null : /* @__PURE__ */ jsx3("div", { role: "alert", children: form.translate(errorMessageKey) })) : null
|
|
1932
2001
|
] })
|
|
1933
2002
|
] });
|
|
1934
2003
|
}
|