@form-engine-ts/react 4.0.0 → 4.2.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 +5 -0
- package/dist/index.cjs +226 -21
- package/dist/index.d.cts +40 -2
- package/dist/index.d.ts +40 -2
- package/dist/index.js +224 -21
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,11 @@ controls `pages`, `localization`, and `conditions` authoring surfaces; each defa
|
|
|
72
72
|
set, locale addition becomes a selector containing only unregistered allowed locales, and the action is disabled at
|
|
73
73
|
`maxLocales`.
|
|
74
74
|
|
|
75
|
+
`fieldEditorControls` makes the standard field editor's title, description, required flag, type selector, options,
|
|
76
|
+
display conditions, text limits, rating bounds, and number limits independently `editable`, `readOnly`, or `hidden`.
|
|
77
|
+
`fieldTypeOptions` can provide an explicit order, comparator, or transformation for generated type choices; the default
|
|
78
|
+
choices are copied before they are changed.
|
|
79
|
+
|
|
75
80
|
Visual Builder has two independent design-system extension layers. `components` replaces normalized primitives such as
|
|
76
81
|
`Button`, `TextInput`, `TextArea`, `Select`, `Checkbox`, `Section`, and `Fieldset`. `slots` replaces complete authoring
|
|
77
82
|
surfaces: `toolbar`, `fieldEditor`, `optionEditor`, `pages`, `localization`, or `translationActions`. Slot props expose
|
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,8 @@ __export(index_exports, {
|
|
|
29
29
|
createLocalStorageSubmissionAttemptStore: () => createLocalStorageSubmissionAttemptStore,
|
|
30
30
|
createLocalStorageSubmissionReceiptStore: () => createLocalStorageSubmissionReceiptStore,
|
|
31
31
|
isTranslationUnresolved: () => isTranslationUnresolved,
|
|
32
|
+
resolveFieldEditorControls: () => resolveFieldEditorControls,
|
|
33
|
+
resolveFieldTypeSelectOptions: () => resolveFieldTypeSelectOptions,
|
|
32
34
|
resolveInitialFieldType: () => resolveInitialFieldType,
|
|
33
35
|
resolveTranslation: () => resolveTranslation,
|
|
34
36
|
submissionReceiptQueryKey: () => submissionReceiptQueryKey,
|
|
@@ -167,6 +169,61 @@ function defaultCreateOption(field, id) {
|
|
|
167
169
|
function defaultCreatePage(id, questionIds) {
|
|
168
170
|
return { id, title: "New page", questionIds };
|
|
169
171
|
}
|
|
172
|
+
function applyFieldConstraintDefaults(field, policy) {
|
|
173
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
174
|
+
if (constraint === void 0) return field;
|
|
175
|
+
const required = constraint.fixedRequired ?? constraint.defaultRequired ?? field.required;
|
|
176
|
+
if (field.type === "rating") {
|
|
177
|
+
const ratingConstraint = "defaultMin" in constraint || "defaultMax" in constraint || "fixedMin" in constraint || "fixedMax" in constraint ? constraint : void 0;
|
|
178
|
+
const min = ratingConstraint !== void 0 && "fixedMin" in ratingConstraint ? ratingConstraint.fixedMin : ratingConstraint !== void 0 && "defaultMin" in ratingConstraint ? ratingConstraint.defaultMin : field.min;
|
|
179
|
+
const max = ratingConstraint !== void 0 && "fixedMax" in ratingConstraint ? ratingConstraint.fixedMax : ratingConstraint !== void 0 && "defaultMax" in ratingConstraint ? ratingConstraint.defaultMax : field.max;
|
|
180
|
+
return {
|
|
181
|
+
...field,
|
|
182
|
+
required,
|
|
183
|
+
...min === void 0 ? {} : { min },
|
|
184
|
+
...max === void 0 ? {} : { max }
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if ((field.type === "text" || field.type === "textarea") && "defaultMaxLength" in constraint) {
|
|
188
|
+
return {
|
|
189
|
+
...field,
|
|
190
|
+
required,
|
|
191
|
+
...constraint.defaultMaxLength === void 0 ? {} : { maxLength: constraint.defaultMaxLength }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { ...field, required };
|
|
195
|
+
}
|
|
196
|
+
function fieldConstraintError(updated, policy) {
|
|
197
|
+
const constraint = policy?.fieldConstraints?.[updated.type];
|
|
198
|
+
if (constraint === void 0) return void 0;
|
|
199
|
+
if (constraint.fixedRequired !== void 0 && updated.required !== constraint.fixedRequired) {
|
|
200
|
+
return { type: "field_constraint_immutable" };
|
|
201
|
+
}
|
|
202
|
+
if (updated.type === "rating") {
|
|
203
|
+
if ("fixedMin" in constraint && constraint.fixedMin !== void 0 && updated.min !== constraint.fixedMin) {
|
|
204
|
+
return { type: "field_constraint_immutable" };
|
|
205
|
+
}
|
|
206
|
+
if ("fixedMax" in constraint && constraint.fixedMax !== void 0 && updated.max !== constraint.fixedMax) {
|
|
207
|
+
return { type: "field_constraint_immutable" };
|
|
208
|
+
}
|
|
209
|
+
if ("allowedMinRange" in constraint && constraint.allowedMinRange !== void 0 && typeof updated.min === "number" && (updated.min < constraint.allowedMinRange[0] || updated.min > constraint.allowedMinRange[1])) {
|
|
210
|
+
return { type: "field_constraint_violation", property: "min", expected: constraint.allowedMinRange[0] };
|
|
211
|
+
}
|
|
212
|
+
if ("allowedMaxRange" in constraint && constraint.allowedMaxRange !== void 0 && typeof updated.max === "number" && (updated.max < constraint.allowedMaxRange[0] || updated.max > constraint.allowedMaxRange[1])) {
|
|
213
|
+
return { type: "field_constraint_violation", property: "max", expected: constraint.allowedMaxRange[1] };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if ((updated.type === "text" || updated.type === "textarea") && "maxMaxLength" in constraint && constraint.maxMaxLength !== void 0 && updated.maxLength !== void 0 && updated.maxLength > constraint.maxMaxLength) {
|
|
217
|
+
return { type: "field_constraint_violation", property: "maxLength", expected: constraint.maxMaxLength };
|
|
218
|
+
}
|
|
219
|
+
if ((updated.type === "select" || updated.type === "radio" || updated.type === "multi-select") && "minOptions" in constraint && constraint.minOptions !== void 0 && updated.options.length < constraint.minOptions) {
|
|
220
|
+
return { type: "field_constraint_violation", property: "options", expected: constraint.minOptions };
|
|
221
|
+
}
|
|
222
|
+
if ((updated.type === "select" || updated.type === "radio" || updated.type === "multi-select") && "maxOptions" in constraint && constraint.maxOptions !== void 0 && updated.options.length > constraint.maxOptions) {
|
|
223
|
+
return { type: "field_constraint_violation", property: "options", expected: constraint.maxOptions };
|
|
224
|
+
}
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
170
227
|
function move(items, sourceIndex, targetIndex) {
|
|
171
228
|
if (sourceIndex < 0 || targetIndex < 0 || targetIndex >= items.length || sourceIndex === targetIndex)
|
|
172
229
|
return void 0;
|
|
@@ -222,6 +279,8 @@ function useFormBuilder({
|
|
|
222
279
|
const updated = updater(current);
|
|
223
280
|
if (updated.id !== fieldId)
|
|
224
281
|
return { success: false, error: { type: "invalid_id", kind: "field", id: updated.id } };
|
|
282
|
+
const constraintError = fieldConstraintError(updated, policy);
|
|
283
|
+
if (constraintError !== void 0) return { success: false, error: constraintError };
|
|
225
284
|
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type))
|
|
226
285
|
return { success: false, error: { type: "disallowed_field_type", fieldType: updated.type } };
|
|
227
286
|
for (const text of [updated.title, updated.description]) {
|
|
@@ -233,7 +292,7 @@ function useFormBuilder({
|
|
|
233
292
|
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
234
293
|
return { success: true };
|
|
235
294
|
},
|
|
236
|
-
[onChange, policy
|
|
295
|
+
[onChange, policy, schema, textPolicyError]
|
|
237
296
|
);
|
|
238
297
|
const updateOption = (0, import_react.useCallback)(
|
|
239
298
|
(fieldId, optionId, updater) => {
|
|
@@ -306,6 +365,7 @@ function useFormBuilder({
|
|
|
306
365
|
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
307
366
|
field = { ...field, options: [option] };
|
|
308
367
|
}
|
|
368
|
+
field = applyFieldConstraintDefaults(field, policy);
|
|
309
369
|
const pages = schema.pages?.map((page, index) => ({
|
|
310
370
|
...page,
|
|
311
371
|
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, field.id] : page.questionIds
|
|
@@ -358,6 +418,12 @@ function useFormBuilder({
|
|
|
358
418
|
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
359
419
|
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField)
|
|
360
420
|
return { success: false, error: { type: "max_options_exceeded", max: policy.maxOptionsPerField } };
|
|
421
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
422
|
+
if (constraint !== void 0 && "maxOptions" in constraint && constraint.maxOptions !== void 0 && field.options.length >= constraint.maxOptions)
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: { type: "field_constraint_violation", property: "options", expected: constraint.maxOptions }
|
|
426
|
+
};
|
|
361
427
|
const ids = new Set(
|
|
362
428
|
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
363
429
|
);
|
|
@@ -374,7 +440,7 @@ function useFormBuilder({
|
|
|
374
440
|
});
|
|
375
441
|
return { success: true };
|
|
376
442
|
},
|
|
377
|
-
[createId, factories.createOption, onChange, policy?.maxOptionsPerField, schema]
|
|
443
|
+
[createId, factories.createOption, onChange, policy, policy?.maxOptionsPerField, schema]
|
|
378
444
|
);
|
|
379
445
|
const removeOption = (0, import_react.useCallback)(
|
|
380
446
|
(fieldId, optionId) => {
|
|
@@ -384,6 +450,12 @@ function useFormBuilder({
|
|
|
384
450
|
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
385
451
|
if (field.options.length <= 1)
|
|
386
452
|
return { success: false, error: { type: "invalid_operation", message: "A choice field needs one option." } };
|
|
453
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
454
|
+
if (constraint !== void 0 && "minOptions" in constraint && constraint.minOptions !== void 0 && field.options.length <= constraint.minOptions)
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: { type: "field_constraint_violation", property: "options", expected: constraint.minOptions }
|
|
458
|
+
};
|
|
387
459
|
onChange({
|
|
388
460
|
...schema,
|
|
389
461
|
fields: schema.fields.map(
|
|
@@ -392,7 +464,7 @@ function useFormBuilder({
|
|
|
392
464
|
});
|
|
393
465
|
return { success: true };
|
|
394
466
|
},
|
|
395
|
-
[onChange, schema]
|
|
467
|
+
[onChange, policy, schema]
|
|
396
468
|
);
|
|
397
469
|
const moveOption = (0, import_react.useCallback)(
|
|
398
470
|
(fieldId, optionId, targetIndex) => {
|
|
@@ -423,7 +495,9 @@ function useFormBuilder({
|
|
|
423
495
|
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
424
496
|
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
425
497
|
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
426
|
-
let transformed = (0, import_core.transformFieldType)(field, type);
|
|
498
|
+
let transformed = applyFieldConstraintDefaults((0, import_core.transformFieldType)(field, type), policy);
|
|
499
|
+
const constraintError = fieldConstraintError(transformed, policy);
|
|
500
|
+
if (constraintError !== void 0) return { success: false, error: constraintError };
|
|
427
501
|
if (CHOICE_TYPES.includes(type) && !("options" in field) && "options" in transformed) {
|
|
428
502
|
const ids = new Set(
|
|
429
503
|
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
@@ -438,7 +512,7 @@ function useFormBuilder({
|
|
|
438
512
|
onChange({ ...schema, fields: schema.fields.map((item) => item.id === fieldId ? transformed : item) });
|
|
439
513
|
return { success: true };
|
|
440
514
|
},
|
|
441
|
-
[createId, factories.createOption, onChange, policy?.allowedFieldTypes, schema]
|
|
515
|
+
[createId, factories.createOption, onChange, policy, policy?.allowedFieldTypes, schema]
|
|
442
516
|
);
|
|
443
517
|
const addPage = (0, import_react.useCallback)(
|
|
444
518
|
(questionId) => {
|
|
@@ -1291,6 +1365,34 @@ var FIELD_TYPES = [
|
|
|
1291
1365
|
"checkbox",
|
|
1292
1366
|
"radio"
|
|
1293
1367
|
];
|
|
1368
|
+
var DEFAULT_FIELD_EDITOR_CONTROL_MODE = "editable";
|
|
1369
|
+
function resolveFieldEditorControls(config = {}) {
|
|
1370
|
+
return {
|
|
1371
|
+
title: config.title ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1372
|
+
description: config.description ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1373
|
+
required: config.required ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1374
|
+
typeSelect: config.typeSelect ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1375
|
+
options: config.options ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1376
|
+
displayConditions: config.displayConditions ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1377
|
+
textLimits: config.textLimits ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1378
|
+
ratingBounds: config.ratingBounds ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1379
|
+
numberLimits: config.numberLimits ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function resolveFieldTypeSelectOptions(options, config, context) {
|
|
1383
|
+
let resolved = [...options];
|
|
1384
|
+
if (config?.transform !== void 0) resolved = [...config.transform(resolved, context)];
|
|
1385
|
+
if (config?.order !== void 0) {
|
|
1386
|
+
const ranks = new Map(config.order.map((type, index) => [type, index]));
|
|
1387
|
+
resolved.sort((left, right) => {
|
|
1388
|
+
const leftRank = ranks.get(left.value) ?? config.order?.length ?? 0;
|
|
1389
|
+
const rightRank = ranks.get(right.value) ?? config.order?.length ?? 0;
|
|
1390
|
+
return leftRank - rightRank;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
if (config?.sort !== void 0) resolved.sort((left, right) => config.sort?.(left, right, context) ?? 0);
|
|
1394
|
+
return resolved;
|
|
1395
|
+
}
|
|
1294
1396
|
var BUILDER_DEFAULTS = {
|
|
1295
1397
|
"builder.formBuilder": "Form builder",
|
|
1296
1398
|
"builder.basicSettings": "Basic settings",
|
|
@@ -1309,6 +1411,10 @@ var BUILDER_DEFAULTS = {
|
|
|
1309
1411
|
"builder.required": "Required",
|
|
1310
1412
|
"builder.minimum": "Minimum",
|
|
1311
1413
|
"builder.maximum": "Maximum",
|
|
1414
|
+
"builder.minimumLength": "Minimum length",
|
|
1415
|
+
"builder.maximumLength": "Maximum length",
|
|
1416
|
+
"builder.pattern": "Pattern",
|
|
1417
|
+
"builder.step": "Step",
|
|
1312
1418
|
"builder.options": "Options",
|
|
1313
1419
|
"builder.optionLabel": "\u9078\u629E\u80A2 / Option Label {{index}}",
|
|
1314
1420
|
"builder.optionLabelPlaceholder": "Example: Very satisfied",
|
|
@@ -1566,6 +1672,8 @@ function FormBuilder({
|
|
|
1566
1672
|
createManualTranslationMetadata,
|
|
1567
1673
|
readOnly = false,
|
|
1568
1674
|
features,
|
|
1675
|
+
fieldEditorControls,
|
|
1676
|
+
fieldTypeOptions,
|
|
1569
1677
|
components: componentOverrides,
|
|
1570
1678
|
slots,
|
|
1571
1679
|
sectionOrder,
|
|
@@ -2330,6 +2438,7 @@ function FormBuilder({
|
|
|
2330
2438
|
}
|
|
2331
2439
|
) : null }),
|
|
2332
2440
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BuilderSectionGroup, { name: "questions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__list"), children: schema.fields.map((field, index) => {
|
|
2441
|
+
const controls = resolveFieldEditorControls(fieldEditorControls);
|
|
2333
2442
|
const condition = field.displayCondition;
|
|
2334
2443
|
const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
|
|
2335
2444
|
const availableSources = schema.fields.slice(0, index);
|
|
@@ -2345,6 +2454,8 @@ function FormBuilder({
|
|
|
2345
2454
|
...slots === void 0 ? {} : { slots },
|
|
2346
2455
|
...policy === void 0 ? {} : { policy },
|
|
2347
2456
|
...features === void 0 ? {} : { features },
|
|
2457
|
+
...fieldEditorControls === void 0 ? {} : { fieldEditorControls },
|
|
2458
|
+
...fieldTypeOptions === void 0 ? {} : { fieldTypeOptions },
|
|
2348
2459
|
readOnly,
|
|
2349
2460
|
actions,
|
|
2350
2461
|
components
|
|
@@ -2406,7 +2517,7 @@ function FormBuilder({
|
|
|
2406
2517
|
}
|
|
2407
2518
|
) }),
|
|
2408
2519
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2409
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2520
|
+
controls.title === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2410
2521
|
TextInput,
|
|
2411
2522
|
{
|
|
2412
2523
|
id: `builder-field-${field.id}-title`,
|
|
@@ -2418,34 +2529,56 @@ function FormBuilder({
|
|
|
2418
2529
|
"aria-describedby": field.title.trim().length === 0 ? `builder-field-${field.id}-title-error` : void 0,
|
|
2419
2530
|
value: field.title,
|
|
2420
2531
|
placeholder: translate("builder.questionTitlePlaceholder"),
|
|
2532
|
+
readOnly: controls.title === "readOnly",
|
|
2421
2533
|
onChange: (value) => updateField(field.id, (current) => ({
|
|
2422
2534
|
...current,
|
|
2423
2535
|
title: value.trim().length === 0 ? current.title : value
|
|
2424
2536
|
}))
|
|
2425
2537
|
}
|
|
2426
2538
|
) }),
|
|
2427
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2539
|
+
controls.typeSelect === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2428
2540
|
Select,
|
|
2429
2541
|
{
|
|
2430
2542
|
id: `builder-field-${field.id}-type`,
|
|
2431
2543
|
label: translate("builder.type"),
|
|
2432
2544
|
value: field.type,
|
|
2545
|
+
disabled: controls.typeSelect === "readOnly",
|
|
2433
2546
|
onChange: (value) => changeType(field.id, value),
|
|
2434
|
-
options:
|
|
2435
|
-
|
|
2436
|
-
|
|
2547
|
+
options: resolveFieldTypeSelectOptions(
|
|
2548
|
+
FIELD_TYPES.filter(
|
|
2549
|
+
(type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
|
|
2550
|
+
).map((type) => ({ value: type, label: translate(fieldTypeKey(type)) })),
|
|
2551
|
+
fieldTypeOptions,
|
|
2552
|
+
{ currentType: field.type, allowedTypes: policy?.allowedFieldTypes ?? FIELD_TYPES }
|
|
2553
|
+
)
|
|
2437
2554
|
}
|
|
2438
2555
|
) }),
|
|
2439
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2556
|
+
controls.required === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2440
2557
|
Checkbox,
|
|
2441
2558
|
{
|
|
2442
2559
|
className: builderClass("form-engine-builder__check"),
|
|
2443
2560
|
checked: field.required === true,
|
|
2561
|
+
disabled: controls.required === "readOnly",
|
|
2444
2562
|
onChange: (checked) => updateField(field.id, (current) => ({ ...current, required: checked })),
|
|
2445
2563
|
label: translate("builder.required")
|
|
2446
2564
|
}
|
|
2447
2565
|
)
|
|
2448
2566
|
] }),
|
|
2567
|
+
controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2568
|
+
TextArea,
|
|
2569
|
+
{
|
|
2570
|
+
id: `builder-field-${field.id}-description`,
|
|
2571
|
+
name: `fields.${field.id}.description`,
|
|
2572
|
+
label: translate("builder.description"),
|
|
2573
|
+
value: field.description ?? "",
|
|
2574
|
+
readOnly: controls.description === "readOnly",
|
|
2575
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2576
|
+
if (value.length > 0) return { ...current, description: value };
|
|
2577
|
+
const { description: _description, ...remaining } = current;
|
|
2578
|
+
return remaining;
|
|
2579
|
+
})
|
|
2580
|
+
}
|
|
2581
|
+
) }),
|
|
2449
2582
|
!pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2450
2583
|
Select,
|
|
2451
2584
|
{
|
|
@@ -2481,12 +2614,13 @@ function FormBuilder({
|
|
|
2481
2614
|
})
|
|
2482
2615
|
}
|
|
2483
2616
|
) }),
|
|
2484
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2617
|
+
controls.description === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2485
2618
|
TextInput,
|
|
2486
2619
|
{
|
|
2487
2620
|
id: `builder-field-${field.id}-${editingLocale}-description`,
|
|
2488
2621
|
label: translate("builder.pageDescription"),
|
|
2489
2622
|
value: field.translations?.[editingLocale]?.description ?? "",
|
|
2623
|
+
readOnly: controls.description === "readOnly",
|
|
2490
2624
|
onChange: (value) => updateManualTranslation({
|
|
2491
2625
|
locale: editingLocale,
|
|
2492
2626
|
kind: "field",
|
|
@@ -2521,7 +2655,7 @@ function FormBuilder({
|
|
|
2521
2655
|
}
|
|
2522
2656
|
) }, option.id)) : null
|
|
2523
2657
|
] }),
|
|
2524
|
-
field.type === "rating" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2658
|
+
field.type === "rating" && controls.ratingBounds !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2525
2659
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2526
2660
|
TextInput,
|
|
2527
2661
|
{
|
|
@@ -2529,6 +2663,7 @@ function FormBuilder({
|
|
|
2529
2663
|
label: translate("builder.minimum"),
|
|
2530
2664
|
type: "number",
|
|
2531
2665
|
value: String(field.min ?? 1),
|
|
2666
|
+
readOnly: controls.ratingBounds === "readOnly",
|
|
2532
2667
|
onChange: (value) => {
|
|
2533
2668
|
const min = Number(value);
|
|
2534
2669
|
if (!Number.isInteger(min)) return;
|
|
@@ -2546,6 +2681,7 @@ function FormBuilder({
|
|
|
2546
2681
|
label: translate("builder.maximum"),
|
|
2547
2682
|
type: "number",
|
|
2548
2683
|
value: String(field.max ?? 5),
|
|
2684
|
+
readOnly: controls.ratingBounds === "readOnly",
|
|
2549
2685
|
onChange: (value) => {
|
|
2550
2686
|
const max = Number(value);
|
|
2551
2687
|
if (!Number.isInteger(max)) return;
|
|
@@ -2557,7 +2693,73 @@ function FormBuilder({
|
|
|
2557
2693
|
}
|
|
2558
2694
|
) })
|
|
2559
2695
|
] }) : null,
|
|
2560
|
-
"
|
|
2696
|
+
(field.type === "text" || field.type === "textarea") && controls.textLimits !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2697
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2698
|
+
TextInput,
|
|
2699
|
+
{
|
|
2700
|
+
id: `builder-field-${field.id}-min-length`,
|
|
2701
|
+
label: translate("builder.minimumLength"),
|
|
2702
|
+
type: "number",
|
|
2703
|
+
value: field.minLength === void 0 ? "" : String(field.minLength),
|
|
2704
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2705
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2706
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2707
|
+
const parsed = value.trim().length === 0 ? void 0 : Number(value);
|
|
2708
|
+
return parsed === void 0 || !Number.isInteger(parsed) || parsed < 0 ? current : { ...current, minLength: parsed };
|
|
2709
|
+
})
|
|
2710
|
+
}
|
|
2711
|
+
) }),
|
|
2712
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2713
|
+
TextInput,
|
|
2714
|
+
{
|
|
2715
|
+
id: `builder-field-${field.id}-max-length`,
|
|
2716
|
+
label: translate("builder.maximumLength"),
|
|
2717
|
+
type: "number",
|
|
2718
|
+
value: field.maxLength === void 0 ? "" : String(field.maxLength),
|
|
2719
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2720
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2721
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2722
|
+
const parsed = value.trim().length === 0 ? void 0 : Number(value);
|
|
2723
|
+
return parsed === void 0 || !Number.isInteger(parsed) || parsed < 0 ? current : { ...current, maxLength: parsed };
|
|
2724
|
+
})
|
|
2725
|
+
}
|
|
2726
|
+
) }),
|
|
2727
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2728
|
+
TextInput,
|
|
2729
|
+
{
|
|
2730
|
+
id: `builder-field-${field.id}-pattern`,
|
|
2731
|
+
label: translate("builder.pattern"),
|
|
2732
|
+
value: field.pattern ?? "",
|
|
2733
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2734
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2735
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2736
|
+
return value.length === 0 ? current : { ...current, pattern: value };
|
|
2737
|
+
})
|
|
2738
|
+
}
|
|
2739
|
+
) })
|
|
2740
|
+
] }) : null,
|
|
2741
|
+
field.type === "number" && controls.numberLimits !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__grid"), children: ["min", "max", "step"].map((property) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2742
|
+
TextInput,
|
|
2743
|
+
{
|
|
2744
|
+
id: `builder-field-${field.id}-${property}`,
|
|
2745
|
+
label: translate(
|
|
2746
|
+
property === "step" ? "builder.step" : property === "min" ? "builder.minimum" : "builder.maximum"
|
|
2747
|
+
),
|
|
2748
|
+
type: "number",
|
|
2749
|
+
value: field[property] === void 0 ? "" : String(field[property]),
|
|
2750
|
+
readOnly: controls.numberLimits === "readOnly",
|
|
2751
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2752
|
+
if (current.type !== "number") return current;
|
|
2753
|
+
if (value.trim().length === 0) {
|
|
2754
|
+
const { [property]: _removed, ...remaining } = current;
|
|
2755
|
+
return remaining;
|
|
2756
|
+
}
|
|
2757
|
+
const parsed = Number(value);
|
|
2758
|
+
return Number.isFinite(parsed) ? { ...current, [property]: parsed } : current;
|
|
2759
|
+
})
|
|
2760
|
+
}
|
|
2761
|
+
) }, property)) }) : null,
|
|
2762
|
+
"options" in field && controls.options !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__options"), children: [
|
|
2561
2763
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: translate("builder.options") }),
|
|
2562
2764
|
field.options.map(
|
|
2563
2765
|
(option, optionIndex) => OptionEditorSlot === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__option"), children: [
|
|
@@ -2568,6 +2770,7 @@ function FormBuilder({
|
|
|
2568
2770
|
label: translate("builder.optionLabel", { index: optionIndex + 1 }),
|
|
2569
2771
|
value: option.label,
|
|
2570
2772
|
placeholder: translate("builder.optionLabelPlaceholder"),
|
|
2773
|
+
readOnly: controls.options === "readOnly",
|
|
2571
2774
|
onChange: (value) => value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, value)
|
|
2572
2775
|
}
|
|
2573
2776
|
),
|
|
@@ -2577,7 +2780,7 @@ function FormBuilder({
|
|
|
2577
2780
|
{
|
|
2578
2781
|
actionType: "moveUp",
|
|
2579
2782
|
title: translate("builder.moveUp", { title: option.label }),
|
|
2580
|
-
disabled: optionIndex === 0,
|
|
2783
|
+
disabled: controls.options === "readOnly" || optionIndex === 0,
|
|
2581
2784
|
onClick: () => moveOption(field.id, option.id, optionIndex - 1)
|
|
2582
2785
|
}
|
|
2583
2786
|
),
|
|
@@ -2586,7 +2789,7 @@ function FormBuilder({
|
|
|
2586
2789
|
{
|
|
2587
2790
|
actionType: "moveDown",
|
|
2588
2791
|
title: translate("builder.moveDown", { title: option.label }),
|
|
2589
|
-
disabled: optionIndex === field.options.length - 1,
|
|
2792
|
+
disabled: controls.options === "readOnly" || optionIndex === field.options.length - 1,
|
|
2590
2793
|
onClick: () => moveOption(field.id, option.id, optionIndex + 1)
|
|
2591
2794
|
}
|
|
2592
2795
|
),
|
|
@@ -2594,7 +2797,7 @@ function FormBuilder({
|
|
|
2594
2797
|
IconButton,
|
|
2595
2798
|
{
|
|
2596
2799
|
actionType: "delete",
|
|
2597
|
-
disabled: field.options.length === 1,
|
|
2800
|
+
disabled: controls.options === "readOnly" || field.options.length === 1,
|
|
2598
2801
|
onClick: () => removeOption(field.id, option.id),
|
|
2599
2802
|
title: translate("builder.remove")
|
|
2600
2803
|
}
|
|
@@ -2612,7 +2815,7 @@ function FormBuilder({
|
|
|
2612
2815
|
onMoveUp: () => moveOption(field.id, option.id, optionIndex - 1),
|
|
2613
2816
|
onMoveDown: () => moveOption(field.id, option.id, optionIndex + 1),
|
|
2614
2817
|
onRemove: () => removeOption(field.id, option.id),
|
|
2615
|
-
readOnly,
|
|
2818
|
+
readOnly: readOnly || controls.options === "readOnly",
|
|
2616
2819
|
actions,
|
|
2617
2820
|
components
|
|
2618
2821
|
}
|
|
@@ -2626,7 +2829,7 @@ function FormBuilder({
|
|
|
2626
2829
|
index: optionIndex,
|
|
2627
2830
|
currentLocale: editingLocale,
|
|
2628
2831
|
translate,
|
|
2629
|
-
readOnly,
|
|
2832
|
+
readOnly: readOnly || controls.options === "readOnly",
|
|
2630
2833
|
actions,
|
|
2631
2834
|
components
|
|
2632
2835
|
},
|
|
@@ -2638,13 +2841,13 @@ function FormBuilder({
|
|
|
2638
2841
|
{
|
|
2639
2842
|
action: "addOption",
|
|
2640
2843
|
targetId: field.id,
|
|
2641
|
-
disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
2844
|
+
disabled: controls.options === "readOnly" || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
2642
2845
|
onClick: () => addOption(field.id),
|
|
2643
2846
|
children: translate("builder.addOption")
|
|
2644
2847
|
}
|
|
2645
2848
|
)
|
|
2646
2849
|
] }) : null,
|
|
2647
|
-
conditionsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__condition"), children: [
|
|
2850
|
+
conditionsEnabled && controls.displayConditions !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: builderClass("form-engine-builder__condition"), children: [
|
|
2648
2851
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2649
2852
|
Select,
|
|
2650
2853
|
{
|
|
@@ -4001,6 +4204,8 @@ function FormRenderer(props) {
|
|
|
4001
4204
|
createLocalStorageSubmissionAttemptStore,
|
|
4002
4205
|
createLocalStorageSubmissionReceiptStore,
|
|
4003
4206
|
isTranslationUnresolved,
|
|
4207
|
+
resolveFieldEditorControls,
|
|
4208
|
+
resolveFieldTypeSelectOptions,
|
|
4004
4209
|
resolveInitialFieldType,
|
|
4005
4210
|
resolveTranslation,
|
|
4006
4211
|
submissionReceiptQueryKey,
|
package/dist/index.d.cts
CHANGED
|
@@ -60,6 +60,12 @@ type BuilderActionError = {
|
|
|
60
60
|
} | {
|
|
61
61
|
readonly type: "max_locales_exceeded";
|
|
62
62
|
readonly max: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: "field_constraint_immutable";
|
|
65
|
+
} | {
|
|
66
|
+
readonly type: "field_constraint_violation";
|
|
67
|
+
readonly property: string;
|
|
68
|
+
readonly expected: number;
|
|
63
69
|
} | {
|
|
64
70
|
readonly type: "node_not_found";
|
|
65
71
|
readonly kind: BuilderTextTarget["kind"];
|
|
@@ -192,6 +198,32 @@ interface BuilderSelectOption<T extends string = string> {
|
|
|
192
198
|
readonly kind?: string;
|
|
193
199
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
194
200
|
}
|
|
201
|
+
type FieldPropertyControlMode = "editable" | "readOnly" | "hidden";
|
|
202
|
+
interface FieldEditorControlsConfig {
|
|
203
|
+
readonly title?: FieldPropertyControlMode;
|
|
204
|
+
readonly description?: FieldPropertyControlMode;
|
|
205
|
+
readonly required?: FieldPropertyControlMode;
|
|
206
|
+
readonly typeSelect?: FieldPropertyControlMode;
|
|
207
|
+
readonly options?: FieldPropertyControlMode;
|
|
208
|
+
readonly displayConditions?: FieldPropertyControlMode;
|
|
209
|
+
readonly textLimits?: FieldPropertyControlMode;
|
|
210
|
+
readonly ratingBounds?: FieldPropertyControlMode;
|
|
211
|
+
readonly numberLimits?: FieldPropertyControlMode;
|
|
212
|
+
}
|
|
213
|
+
interface FieldTypeSelectOptionsContext {
|
|
214
|
+
readonly currentType: QuestionType;
|
|
215
|
+
readonly allowedTypes: readonly QuestionType[];
|
|
216
|
+
}
|
|
217
|
+
type FieldTypeSelectOptionsTransformer = (options: readonly BuilderSelectOption<QuestionType>[], context: FieldTypeSelectOptionsContext) => readonly BuilderSelectOption<QuestionType>[];
|
|
218
|
+
type FieldTypeSelectOptionsSorter = (left: BuilderSelectOption<QuestionType>, right: BuilderSelectOption<QuestionType>, context: FieldTypeSelectOptionsContext) => number;
|
|
219
|
+
interface FieldTypeSelectOptionsConfig {
|
|
220
|
+
/** Transform the generated choices. The returned array is used as-is. */
|
|
221
|
+
readonly transform?: FieldTypeSelectOptionsTransformer;
|
|
222
|
+
/** Sort generated choices after transform. The source array is never mutated. */
|
|
223
|
+
readonly sort?: FieldTypeSelectOptionsSorter;
|
|
224
|
+
/** Optional explicit order applied before `sort`. */
|
|
225
|
+
readonly order?: readonly QuestionType[];
|
|
226
|
+
}
|
|
195
227
|
interface SelectComponentProps<T extends string = string> extends Omit<InputComponentProps, "value" | "onChange"> {
|
|
196
228
|
readonly value: T;
|
|
197
229
|
readonly onChange: (value: T) => void;
|
|
@@ -278,6 +310,8 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
|
|
|
278
310
|
readonly localization?: boolean;
|
|
279
311
|
readonly conditions?: boolean;
|
|
280
312
|
};
|
|
313
|
+
readonly fieldEditorControls?: FieldEditorControlsConfig;
|
|
314
|
+
readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
|
|
281
315
|
readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
|
|
282
316
|
}
|
|
283
317
|
interface FieldTypeSelectSlotProps {
|
|
@@ -524,6 +558,8 @@ interface SubmissionProtectionProps {
|
|
|
524
558
|
}
|
|
525
559
|
type BeforeSubmit = (values: Readonly<Record<string, unknown>>) => "continue" | "cancel" | Promise<"continue" | "cancel">;
|
|
526
560
|
|
|
561
|
+
declare function resolveFieldEditorControls(config?: FieldEditorControlsConfig): Required<FieldEditorControlsConfig>;
|
|
562
|
+
declare function resolveFieldTypeSelectOptions(options: readonly BuilderSelectOption<QuestionType>[], config: FieldTypeSelectOptionsConfig | undefined, context: FieldTypeSelectOptionsContext): readonly BuilderSelectOption<QuestionType>[];
|
|
527
563
|
declare function resolveInitialFieldType(defaultType?: QuestionType, allowedTypes?: readonly QuestionType[]): QuestionType | null;
|
|
528
564
|
interface FormBuilderFeatures {
|
|
529
565
|
readonly pages?: boolean;
|
|
@@ -547,13 +583,15 @@ interface FormBuilderProps {
|
|
|
547
583
|
readonly createManualTranslationMetadata?: (context: ManualTranslationContext) => Readonly<Record<string, JsonValue>> | undefined;
|
|
548
584
|
readonly readOnly?: boolean;
|
|
549
585
|
readonly features?: FormBuilderFeatures;
|
|
586
|
+
readonly fieldEditorControls?: FieldEditorControlsConfig;
|
|
587
|
+
readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
|
|
550
588
|
readonly components?: FormBuilderComponents;
|
|
551
589
|
readonly slots?: FormBuilderSlots;
|
|
552
590
|
readonly sectionOrder?: readonly FormBuilderSectionName[];
|
|
553
591
|
readonly disableDefaultStyles?: boolean;
|
|
554
592
|
readonly unstyled?: boolean;
|
|
555
593
|
}
|
|
556
|
-
declare function FormBuilder({ schema, onChange, locale, translator, translationAdapter, translationOptions, onTranslationReport, policy, idFactory, factories, className, defaultFieldType, onActionError, createManualTranslationMetadata, readOnly, features, components: componentOverrides, slots, sectionOrder, disableDefaultStyles, unstyled }: FormBuilderProps): react.JSX.Element;
|
|
594
|
+
declare function FormBuilder({ schema, onChange, locale, translator, translationAdapter, translationOptions, onTranslationReport, policy, idFactory, factories, className, defaultFieldType, onActionError, createManualTranslationMetadata, readOnly, features, fieldEditorControls, fieldTypeOptions, components: componentOverrides, slots, sectionOrder, disableDefaultStyles, unstyled }: FormBuilderProps): react.JSX.Element;
|
|
557
595
|
|
|
558
596
|
type SubmitStatus = "idle" | "submitting" | "success" | "error";
|
|
559
597
|
interface FormContextValue {
|
|
@@ -659,4 +697,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
659
697
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
660
698
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
661
699
|
|
|
662
|
-
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
|
700
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,12 @@ type BuilderActionError = {
|
|
|
60
60
|
} | {
|
|
61
61
|
readonly type: "max_locales_exceeded";
|
|
62
62
|
readonly max: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: "field_constraint_immutable";
|
|
65
|
+
} | {
|
|
66
|
+
readonly type: "field_constraint_violation";
|
|
67
|
+
readonly property: string;
|
|
68
|
+
readonly expected: number;
|
|
63
69
|
} | {
|
|
64
70
|
readonly type: "node_not_found";
|
|
65
71
|
readonly kind: BuilderTextTarget["kind"];
|
|
@@ -192,6 +198,32 @@ interface BuilderSelectOption<T extends string = string> {
|
|
|
192
198
|
readonly kind?: string;
|
|
193
199
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
194
200
|
}
|
|
201
|
+
type FieldPropertyControlMode = "editable" | "readOnly" | "hidden";
|
|
202
|
+
interface FieldEditorControlsConfig {
|
|
203
|
+
readonly title?: FieldPropertyControlMode;
|
|
204
|
+
readonly description?: FieldPropertyControlMode;
|
|
205
|
+
readonly required?: FieldPropertyControlMode;
|
|
206
|
+
readonly typeSelect?: FieldPropertyControlMode;
|
|
207
|
+
readonly options?: FieldPropertyControlMode;
|
|
208
|
+
readonly displayConditions?: FieldPropertyControlMode;
|
|
209
|
+
readonly textLimits?: FieldPropertyControlMode;
|
|
210
|
+
readonly ratingBounds?: FieldPropertyControlMode;
|
|
211
|
+
readonly numberLimits?: FieldPropertyControlMode;
|
|
212
|
+
}
|
|
213
|
+
interface FieldTypeSelectOptionsContext {
|
|
214
|
+
readonly currentType: QuestionType;
|
|
215
|
+
readonly allowedTypes: readonly QuestionType[];
|
|
216
|
+
}
|
|
217
|
+
type FieldTypeSelectOptionsTransformer = (options: readonly BuilderSelectOption<QuestionType>[], context: FieldTypeSelectOptionsContext) => readonly BuilderSelectOption<QuestionType>[];
|
|
218
|
+
type FieldTypeSelectOptionsSorter = (left: BuilderSelectOption<QuestionType>, right: BuilderSelectOption<QuestionType>, context: FieldTypeSelectOptionsContext) => number;
|
|
219
|
+
interface FieldTypeSelectOptionsConfig {
|
|
220
|
+
/** Transform the generated choices. The returned array is used as-is. */
|
|
221
|
+
readonly transform?: FieldTypeSelectOptionsTransformer;
|
|
222
|
+
/** Sort generated choices after transform. The source array is never mutated. */
|
|
223
|
+
readonly sort?: FieldTypeSelectOptionsSorter;
|
|
224
|
+
/** Optional explicit order applied before `sort`. */
|
|
225
|
+
readonly order?: readonly QuestionType[];
|
|
226
|
+
}
|
|
195
227
|
interface SelectComponentProps<T extends string = string> extends Omit<InputComponentProps, "value" | "onChange"> {
|
|
196
228
|
readonly value: T;
|
|
197
229
|
readonly onChange: (value: T) => void;
|
|
@@ -278,6 +310,8 @@ interface BuilderFieldEditorSlotProps extends BuilderSlotBaseProps {
|
|
|
278
310
|
readonly localization?: boolean;
|
|
279
311
|
readonly conditions?: boolean;
|
|
280
312
|
};
|
|
313
|
+
readonly fieldEditorControls?: FieldEditorControlsConfig;
|
|
314
|
+
readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
|
|
281
315
|
readonly slots?: Pick<FormBuilderSlots, "fieldTypeSelect" | "fieldEditorHeader">;
|
|
282
316
|
}
|
|
283
317
|
interface FieldTypeSelectSlotProps {
|
|
@@ -524,6 +558,8 @@ interface SubmissionProtectionProps {
|
|
|
524
558
|
}
|
|
525
559
|
type BeforeSubmit = (values: Readonly<Record<string, unknown>>) => "continue" | "cancel" | Promise<"continue" | "cancel">;
|
|
526
560
|
|
|
561
|
+
declare function resolveFieldEditorControls(config?: FieldEditorControlsConfig): Required<FieldEditorControlsConfig>;
|
|
562
|
+
declare function resolveFieldTypeSelectOptions(options: readonly BuilderSelectOption<QuestionType>[], config: FieldTypeSelectOptionsConfig | undefined, context: FieldTypeSelectOptionsContext): readonly BuilderSelectOption<QuestionType>[];
|
|
527
563
|
declare function resolveInitialFieldType(defaultType?: QuestionType, allowedTypes?: readonly QuestionType[]): QuestionType | null;
|
|
528
564
|
interface FormBuilderFeatures {
|
|
529
565
|
readonly pages?: boolean;
|
|
@@ -547,13 +583,15 @@ interface FormBuilderProps {
|
|
|
547
583
|
readonly createManualTranslationMetadata?: (context: ManualTranslationContext) => Readonly<Record<string, JsonValue>> | undefined;
|
|
548
584
|
readonly readOnly?: boolean;
|
|
549
585
|
readonly features?: FormBuilderFeatures;
|
|
586
|
+
readonly fieldEditorControls?: FieldEditorControlsConfig;
|
|
587
|
+
readonly fieldTypeOptions?: FieldTypeSelectOptionsConfig;
|
|
550
588
|
readonly components?: FormBuilderComponents;
|
|
551
589
|
readonly slots?: FormBuilderSlots;
|
|
552
590
|
readonly sectionOrder?: readonly FormBuilderSectionName[];
|
|
553
591
|
readonly disableDefaultStyles?: boolean;
|
|
554
592
|
readonly unstyled?: boolean;
|
|
555
593
|
}
|
|
556
|
-
declare function FormBuilder({ schema, onChange, locale, translator, translationAdapter, translationOptions, onTranslationReport, policy, idFactory, factories, className, defaultFieldType, onActionError, createManualTranslationMetadata, readOnly, features, components: componentOverrides, slots, sectionOrder, disableDefaultStyles, unstyled }: FormBuilderProps): react.JSX.Element;
|
|
594
|
+
declare function FormBuilder({ schema, onChange, locale, translator, translationAdapter, translationOptions, onTranslationReport, policy, idFactory, factories, className, defaultFieldType, onActionError, createManualTranslationMetadata, readOnly, features, fieldEditorControls, fieldTypeOptions, components: componentOverrides, slots, sectionOrder, disableDefaultStyles, unstyled }: FormBuilderProps): react.JSX.Element;
|
|
557
595
|
|
|
558
596
|
type SubmitStatus = "idle" | "submitting" | "success" | "error";
|
|
559
597
|
interface FormContextValue {
|
|
@@ -659,4 +697,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
659
697
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
660
698
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
661
699
|
|
|
662
|
-
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorHeaderSlotProps, type FieldState, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
|
700
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ComponentBaseProps, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type UseSubmissionReceiptsResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts };
|
package/dist/index.js
CHANGED
|
@@ -133,6 +133,61 @@ function defaultCreateOption(field, id) {
|
|
|
133
133
|
function defaultCreatePage(id, questionIds) {
|
|
134
134
|
return { id, title: "New page", questionIds };
|
|
135
135
|
}
|
|
136
|
+
function applyFieldConstraintDefaults(field, policy) {
|
|
137
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
138
|
+
if (constraint === void 0) return field;
|
|
139
|
+
const required = constraint.fixedRequired ?? constraint.defaultRequired ?? field.required;
|
|
140
|
+
if (field.type === "rating") {
|
|
141
|
+
const ratingConstraint = "defaultMin" in constraint || "defaultMax" in constraint || "fixedMin" in constraint || "fixedMax" in constraint ? constraint : void 0;
|
|
142
|
+
const min = ratingConstraint !== void 0 && "fixedMin" in ratingConstraint ? ratingConstraint.fixedMin : ratingConstraint !== void 0 && "defaultMin" in ratingConstraint ? ratingConstraint.defaultMin : field.min;
|
|
143
|
+
const max = ratingConstraint !== void 0 && "fixedMax" in ratingConstraint ? ratingConstraint.fixedMax : ratingConstraint !== void 0 && "defaultMax" in ratingConstraint ? ratingConstraint.defaultMax : field.max;
|
|
144
|
+
return {
|
|
145
|
+
...field,
|
|
146
|
+
required,
|
|
147
|
+
...min === void 0 ? {} : { min },
|
|
148
|
+
...max === void 0 ? {} : { max }
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if ((field.type === "text" || field.type === "textarea") && "defaultMaxLength" in constraint) {
|
|
152
|
+
return {
|
|
153
|
+
...field,
|
|
154
|
+
required,
|
|
155
|
+
...constraint.defaultMaxLength === void 0 ? {} : { maxLength: constraint.defaultMaxLength }
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { ...field, required };
|
|
159
|
+
}
|
|
160
|
+
function fieldConstraintError(updated, policy) {
|
|
161
|
+
const constraint = policy?.fieldConstraints?.[updated.type];
|
|
162
|
+
if (constraint === void 0) return void 0;
|
|
163
|
+
if (constraint.fixedRequired !== void 0 && updated.required !== constraint.fixedRequired) {
|
|
164
|
+
return { type: "field_constraint_immutable" };
|
|
165
|
+
}
|
|
166
|
+
if (updated.type === "rating") {
|
|
167
|
+
if ("fixedMin" in constraint && constraint.fixedMin !== void 0 && updated.min !== constraint.fixedMin) {
|
|
168
|
+
return { type: "field_constraint_immutable" };
|
|
169
|
+
}
|
|
170
|
+
if ("fixedMax" in constraint && constraint.fixedMax !== void 0 && updated.max !== constraint.fixedMax) {
|
|
171
|
+
return { type: "field_constraint_immutable" };
|
|
172
|
+
}
|
|
173
|
+
if ("allowedMinRange" in constraint && constraint.allowedMinRange !== void 0 && typeof updated.min === "number" && (updated.min < constraint.allowedMinRange[0] || updated.min > constraint.allowedMinRange[1])) {
|
|
174
|
+
return { type: "field_constraint_violation", property: "min", expected: constraint.allowedMinRange[0] };
|
|
175
|
+
}
|
|
176
|
+
if ("allowedMaxRange" in constraint && constraint.allowedMaxRange !== void 0 && typeof updated.max === "number" && (updated.max < constraint.allowedMaxRange[0] || updated.max > constraint.allowedMaxRange[1])) {
|
|
177
|
+
return { type: "field_constraint_violation", property: "max", expected: constraint.allowedMaxRange[1] };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if ((updated.type === "text" || updated.type === "textarea") && "maxMaxLength" in constraint && constraint.maxMaxLength !== void 0 && updated.maxLength !== void 0 && updated.maxLength > constraint.maxMaxLength) {
|
|
181
|
+
return { type: "field_constraint_violation", property: "maxLength", expected: constraint.maxMaxLength };
|
|
182
|
+
}
|
|
183
|
+
if ((updated.type === "select" || updated.type === "radio" || updated.type === "multi-select") && "minOptions" in constraint && constraint.minOptions !== void 0 && updated.options.length < constraint.minOptions) {
|
|
184
|
+
return { type: "field_constraint_violation", property: "options", expected: constraint.minOptions };
|
|
185
|
+
}
|
|
186
|
+
if ((updated.type === "select" || updated.type === "radio" || updated.type === "multi-select") && "maxOptions" in constraint && constraint.maxOptions !== void 0 && updated.options.length > constraint.maxOptions) {
|
|
187
|
+
return { type: "field_constraint_violation", property: "options", expected: constraint.maxOptions };
|
|
188
|
+
}
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
136
191
|
function move(items, sourceIndex, targetIndex) {
|
|
137
192
|
if (sourceIndex < 0 || targetIndex < 0 || targetIndex >= items.length || sourceIndex === targetIndex)
|
|
138
193
|
return void 0;
|
|
@@ -188,6 +243,8 @@ function useFormBuilder({
|
|
|
188
243
|
const updated = updater(current);
|
|
189
244
|
if (updated.id !== fieldId)
|
|
190
245
|
return { success: false, error: { type: "invalid_id", kind: "field", id: updated.id } };
|
|
246
|
+
const constraintError = fieldConstraintError(updated, policy);
|
|
247
|
+
if (constraintError !== void 0) return { success: false, error: constraintError };
|
|
191
248
|
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(updated.type))
|
|
192
249
|
return { success: false, error: { type: "disallowed_field_type", fieldType: updated.type } };
|
|
193
250
|
for (const text of [updated.title, updated.description]) {
|
|
@@ -199,7 +256,7 @@ function useFormBuilder({
|
|
|
199
256
|
onChange({ ...schema, fields: schema.fields.map((field) => field.id === fieldId ? updated : field) });
|
|
200
257
|
return { success: true };
|
|
201
258
|
},
|
|
202
|
-
[onChange, policy
|
|
259
|
+
[onChange, policy, schema, textPolicyError]
|
|
203
260
|
);
|
|
204
261
|
const updateOption = useCallback(
|
|
205
262
|
(fieldId, optionId, updater) => {
|
|
@@ -272,6 +329,7 @@ function useFormBuilder({
|
|
|
272
329
|
return { success: false, error: { type: "invalid_id", kind: "option", id: option.id } };
|
|
273
330
|
field = { ...field, options: [option] };
|
|
274
331
|
}
|
|
332
|
+
field = applyFieldConstraintDefaults(field, policy);
|
|
275
333
|
const pages = schema.pages?.map((page, index) => ({
|
|
276
334
|
...page,
|
|
277
335
|
questionIds: page.id === pageId || pageId === void 0 && index === (schema.pages?.length ?? 0) - 1 ? [...page.questionIds, field.id] : page.questionIds
|
|
@@ -324,6 +382,12 @@ function useFormBuilder({
|
|
|
324
382
|
return { success: false, error: { type: "invalid_operation", message: `Field ${fieldId} has no options.` } };
|
|
325
383
|
if (policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField)
|
|
326
384
|
return { success: false, error: { type: "max_options_exceeded", max: policy.maxOptionsPerField } };
|
|
385
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
386
|
+
if (constraint !== void 0 && "maxOptions" in constraint && constraint.maxOptions !== void 0 && field.options.length >= constraint.maxOptions)
|
|
387
|
+
return {
|
|
388
|
+
success: false,
|
|
389
|
+
error: { type: "field_constraint_violation", property: "options", expected: constraint.maxOptions }
|
|
390
|
+
};
|
|
327
391
|
const ids = new Set(
|
|
328
392
|
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
329
393
|
);
|
|
@@ -340,7 +404,7 @@ function useFormBuilder({
|
|
|
340
404
|
});
|
|
341
405
|
return { success: true };
|
|
342
406
|
},
|
|
343
|
-
[createId, factories.createOption, onChange, policy?.maxOptionsPerField, schema]
|
|
407
|
+
[createId, factories.createOption, onChange, policy, policy?.maxOptionsPerField, schema]
|
|
344
408
|
);
|
|
345
409
|
const removeOption = useCallback(
|
|
346
410
|
(fieldId, optionId) => {
|
|
@@ -350,6 +414,12 @@ function useFormBuilder({
|
|
|
350
414
|
return { success: false, error: { type: "node_not_found", kind: "option", id: optionId } };
|
|
351
415
|
if (field.options.length <= 1)
|
|
352
416
|
return { success: false, error: { type: "invalid_operation", message: "A choice field needs one option." } };
|
|
417
|
+
const constraint = policy?.fieldConstraints?.[field.type];
|
|
418
|
+
if (constraint !== void 0 && "minOptions" in constraint && constraint.minOptions !== void 0 && field.options.length <= constraint.minOptions)
|
|
419
|
+
return {
|
|
420
|
+
success: false,
|
|
421
|
+
error: { type: "field_constraint_violation", property: "options", expected: constraint.minOptions }
|
|
422
|
+
};
|
|
353
423
|
onChange({
|
|
354
424
|
...schema,
|
|
355
425
|
fields: schema.fields.map(
|
|
@@ -358,7 +428,7 @@ function useFormBuilder({
|
|
|
358
428
|
});
|
|
359
429
|
return { success: true };
|
|
360
430
|
},
|
|
361
|
-
[onChange, schema]
|
|
431
|
+
[onChange, policy, schema]
|
|
362
432
|
);
|
|
363
433
|
const moveOption = useCallback(
|
|
364
434
|
(fieldId, optionId, targetIndex) => {
|
|
@@ -389,7 +459,9 @@ function useFormBuilder({
|
|
|
389
459
|
if (field === void 0) return { success: false, error: { type: "node_not_found", kind: "field", id: fieldId } };
|
|
390
460
|
if (policy?.allowedFieldTypes !== void 0 && !policy.allowedFieldTypes.includes(type))
|
|
391
461
|
return { success: false, error: { type: "disallowed_field_type", fieldType: type } };
|
|
392
|
-
let transformed = transformFieldType(field, type);
|
|
462
|
+
let transformed = applyFieldConstraintDefaults(transformFieldType(field, type), policy);
|
|
463
|
+
const constraintError = fieldConstraintError(transformed, policy);
|
|
464
|
+
if (constraintError !== void 0) return { success: false, error: constraintError };
|
|
393
465
|
if (CHOICE_TYPES.includes(type) && !("options" in field) && "options" in transformed) {
|
|
394
466
|
const ids = new Set(
|
|
395
467
|
schema.fields.flatMap((item) => "options" in item ? item.options.map((option2) => option2.id) : [])
|
|
@@ -404,7 +476,7 @@ function useFormBuilder({
|
|
|
404
476
|
onChange({ ...schema, fields: schema.fields.map((item) => item.id === fieldId ? transformed : item) });
|
|
405
477
|
return { success: true };
|
|
406
478
|
},
|
|
407
|
-
[createId, factories.createOption, onChange, policy?.allowedFieldTypes, schema]
|
|
479
|
+
[createId, factories.createOption, onChange, policy, policy?.allowedFieldTypes, schema]
|
|
408
480
|
);
|
|
409
481
|
const addPage = useCallback(
|
|
410
482
|
(questionId) => {
|
|
@@ -1257,6 +1329,34 @@ var FIELD_TYPES = [
|
|
|
1257
1329
|
"checkbox",
|
|
1258
1330
|
"radio"
|
|
1259
1331
|
];
|
|
1332
|
+
var DEFAULT_FIELD_EDITOR_CONTROL_MODE = "editable";
|
|
1333
|
+
function resolveFieldEditorControls(config = {}) {
|
|
1334
|
+
return {
|
|
1335
|
+
title: config.title ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1336
|
+
description: config.description ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1337
|
+
required: config.required ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1338
|
+
typeSelect: config.typeSelect ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1339
|
+
options: config.options ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1340
|
+
displayConditions: config.displayConditions ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1341
|
+
textLimits: config.textLimits ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1342
|
+
ratingBounds: config.ratingBounds ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE,
|
|
1343
|
+
numberLimits: config.numberLimits ?? DEFAULT_FIELD_EDITOR_CONTROL_MODE
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
function resolveFieldTypeSelectOptions(options, config, context) {
|
|
1347
|
+
let resolved = [...options];
|
|
1348
|
+
if (config?.transform !== void 0) resolved = [...config.transform(resolved, context)];
|
|
1349
|
+
if (config?.order !== void 0) {
|
|
1350
|
+
const ranks = new Map(config.order.map((type, index) => [type, index]));
|
|
1351
|
+
resolved.sort((left, right) => {
|
|
1352
|
+
const leftRank = ranks.get(left.value) ?? config.order?.length ?? 0;
|
|
1353
|
+
const rightRank = ranks.get(right.value) ?? config.order?.length ?? 0;
|
|
1354
|
+
return leftRank - rightRank;
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
if (config?.sort !== void 0) resolved.sort((left, right) => config.sort?.(left, right, context) ?? 0);
|
|
1358
|
+
return resolved;
|
|
1359
|
+
}
|
|
1260
1360
|
var BUILDER_DEFAULTS = {
|
|
1261
1361
|
"builder.formBuilder": "Form builder",
|
|
1262
1362
|
"builder.basicSettings": "Basic settings",
|
|
@@ -1275,6 +1375,10 @@ var BUILDER_DEFAULTS = {
|
|
|
1275
1375
|
"builder.required": "Required",
|
|
1276
1376
|
"builder.minimum": "Minimum",
|
|
1277
1377
|
"builder.maximum": "Maximum",
|
|
1378
|
+
"builder.minimumLength": "Minimum length",
|
|
1379
|
+
"builder.maximumLength": "Maximum length",
|
|
1380
|
+
"builder.pattern": "Pattern",
|
|
1381
|
+
"builder.step": "Step",
|
|
1278
1382
|
"builder.options": "Options",
|
|
1279
1383
|
"builder.optionLabel": "\u9078\u629E\u80A2 / Option Label {{index}}",
|
|
1280
1384
|
"builder.optionLabelPlaceholder": "Example: Very satisfied",
|
|
@@ -1532,6 +1636,8 @@ function FormBuilder({
|
|
|
1532
1636
|
createManualTranslationMetadata,
|
|
1533
1637
|
readOnly = false,
|
|
1534
1638
|
features,
|
|
1639
|
+
fieldEditorControls,
|
|
1640
|
+
fieldTypeOptions,
|
|
1535
1641
|
components: componentOverrides,
|
|
1536
1642
|
slots,
|
|
1537
1643
|
sectionOrder,
|
|
@@ -2296,6 +2402,7 @@ function FormBuilder({
|
|
|
2296
2402
|
}
|
|
2297
2403
|
) : null }),
|
|
2298
2404
|
/* @__PURE__ */ jsx(BuilderSectionGroup, { name: "questions", children: /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__list"), children: schema.fields.map((field, index) => {
|
|
2405
|
+
const controls = resolveFieldEditorControls(fieldEditorControls);
|
|
2299
2406
|
const condition = field.displayCondition;
|
|
2300
2407
|
const source = condition === void 0 ? void 0 : schema.fields.find((item) => item.id === condition.questionId);
|
|
2301
2408
|
const availableSources = schema.fields.slice(0, index);
|
|
@@ -2311,6 +2418,8 @@ function FormBuilder({
|
|
|
2311
2418
|
...slots === void 0 ? {} : { slots },
|
|
2312
2419
|
...policy === void 0 ? {} : { policy },
|
|
2313
2420
|
...features === void 0 ? {} : { features },
|
|
2421
|
+
...fieldEditorControls === void 0 ? {} : { fieldEditorControls },
|
|
2422
|
+
...fieldTypeOptions === void 0 ? {} : { fieldTypeOptions },
|
|
2314
2423
|
readOnly,
|
|
2315
2424
|
actions,
|
|
2316
2425
|
components
|
|
@@ -2372,7 +2481,7 @@ function FormBuilder({
|
|
|
2372
2481
|
}
|
|
2373
2482
|
) }),
|
|
2374
2483
|
/* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2375
|
-
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2484
|
+
controls.title === "hidden" ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2376
2485
|
TextInput,
|
|
2377
2486
|
{
|
|
2378
2487
|
id: `builder-field-${field.id}-title`,
|
|
@@ -2384,34 +2493,56 @@ function FormBuilder({
|
|
|
2384
2493
|
"aria-describedby": field.title.trim().length === 0 ? `builder-field-${field.id}-title-error` : void 0,
|
|
2385
2494
|
value: field.title,
|
|
2386
2495
|
placeholder: translate("builder.questionTitlePlaceholder"),
|
|
2496
|
+
readOnly: controls.title === "readOnly",
|
|
2387
2497
|
onChange: (value) => updateField(field.id, (current) => ({
|
|
2388
2498
|
...current,
|
|
2389
2499
|
title: value.trim().length === 0 ? current.title : value
|
|
2390
2500
|
}))
|
|
2391
2501
|
}
|
|
2392
2502
|
) }),
|
|
2393
|
-
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2503
|
+
controls.typeSelect === "hidden" ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2394
2504
|
Select,
|
|
2395
2505
|
{
|
|
2396
2506
|
id: `builder-field-${field.id}-type`,
|
|
2397
2507
|
label: translate("builder.type"),
|
|
2398
2508
|
value: field.type,
|
|
2509
|
+
disabled: controls.typeSelect === "readOnly",
|
|
2399
2510
|
onChange: (value) => changeType(field.id, value),
|
|
2400
|
-
options:
|
|
2401
|
-
|
|
2402
|
-
|
|
2511
|
+
options: resolveFieldTypeSelectOptions(
|
|
2512
|
+
FIELD_TYPES.filter(
|
|
2513
|
+
(type) => policy?.allowedFieldTypes === void 0 || policy.allowedFieldTypes.includes(type)
|
|
2514
|
+
).map((type) => ({ value: type, label: translate(fieldTypeKey(type)) })),
|
|
2515
|
+
fieldTypeOptions,
|
|
2516
|
+
{ currentType: field.type, allowedTypes: policy?.allowedFieldTypes ?? FIELD_TYPES }
|
|
2517
|
+
)
|
|
2403
2518
|
}
|
|
2404
2519
|
) }),
|
|
2405
|
-
/* @__PURE__ */ jsx(
|
|
2520
|
+
controls.required === "hidden" ? null : /* @__PURE__ */ jsx(
|
|
2406
2521
|
Checkbox,
|
|
2407
2522
|
{
|
|
2408
2523
|
className: builderClass("form-engine-builder__check"),
|
|
2409
2524
|
checked: field.required === true,
|
|
2525
|
+
disabled: controls.required === "readOnly",
|
|
2410
2526
|
onChange: (checked) => updateField(field.id, (current) => ({ ...current, required: checked })),
|
|
2411
2527
|
label: translate("builder.required")
|
|
2412
2528
|
}
|
|
2413
2529
|
)
|
|
2414
2530
|
] }),
|
|
2531
|
+
controls.description === "hidden" ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2532
|
+
TextArea,
|
|
2533
|
+
{
|
|
2534
|
+
id: `builder-field-${field.id}-description`,
|
|
2535
|
+
name: `fields.${field.id}.description`,
|
|
2536
|
+
label: translate("builder.description"),
|
|
2537
|
+
value: field.description ?? "",
|
|
2538
|
+
readOnly: controls.description === "readOnly",
|
|
2539
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2540
|
+
if (value.length > 0) return { ...current, description: value };
|
|
2541
|
+
const { description: _description, ...remaining } = current;
|
|
2542
|
+
return remaining;
|
|
2543
|
+
})
|
|
2544
|
+
}
|
|
2545
|
+
) }),
|
|
2415
2546
|
!pagesEnabled || schema.pages === void 0 ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2416
2547
|
Select,
|
|
2417
2548
|
{
|
|
@@ -2447,12 +2578,13 @@ function FormBuilder({
|
|
|
2447
2578
|
})
|
|
2448
2579
|
}
|
|
2449
2580
|
) }),
|
|
2450
|
-
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2581
|
+
controls.description === "hidden" ? null : /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2451
2582
|
TextInput,
|
|
2452
2583
|
{
|
|
2453
2584
|
id: `builder-field-${field.id}-${editingLocale}-description`,
|
|
2454
2585
|
label: translate("builder.pageDescription"),
|
|
2455
2586
|
value: field.translations?.[editingLocale]?.description ?? "",
|
|
2587
|
+
readOnly: controls.description === "readOnly",
|
|
2456
2588
|
onChange: (value) => updateManualTranslation({
|
|
2457
2589
|
locale: editingLocale,
|
|
2458
2590
|
kind: "field",
|
|
@@ -2487,7 +2619,7 @@ function FormBuilder({
|
|
|
2487
2619
|
}
|
|
2488
2620
|
) }, option.id)) : null
|
|
2489
2621
|
] }),
|
|
2490
|
-
field.type === "rating" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2622
|
+
field.type === "rating" && controls.ratingBounds !== "hidden" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2491
2623
|
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2492
2624
|
TextInput,
|
|
2493
2625
|
{
|
|
@@ -2495,6 +2627,7 @@ function FormBuilder({
|
|
|
2495
2627
|
label: translate("builder.minimum"),
|
|
2496
2628
|
type: "number",
|
|
2497
2629
|
value: String(field.min ?? 1),
|
|
2630
|
+
readOnly: controls.ratingBounds === "readOnly",
|
|
2498
2631
|
onChange: (value) => {
|
|
2499
2632
|
const min = Number(value);
|
|
2500
2633
|
if (!Number.isInteger(min)) return;
|
|
@@ -2512,6 +2645,7 @@ function FormBuilder({
|
|
|
2512
2645
|
label: translate("builder.maximum"),
|
|
2513
2646
|
type: "number",
|
|
2514
2647
|
value: String(field.max ?? 5),
|
|
2648
|
+
readOnly: controls.ratingBounds === "readOnly",
|
|
2515
2649
|
onChange: (value) => {
|
|
2516
2650
|
const max = Number(value);
|
|
2517
2651
|
if (!Number.isInteger(max)) return;
|
|
@@ -2523,7 +2657,73 @@ function FormBuilder({
|
|
|
2523
2657
|
}
|
|
2524
2658
|
) })
|
|
2525
2659
|
] }) : null,
|
|
2526
|
-
"
|
|
2660
|
+
(field.type === "text" || field.type === "textarea") && controls.textLimits !== "hidden" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__grid"), children: [
|
|
2661
|
+
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2662
|
+
TextInput,
|
|
2663
|
+
{
|
|
2664
|
+
id: `builder-field-${field.id}-min-length`,
|
|
2665
|
+
label: translate("builder.minimumLength"),
|
|
2666
|
+
type: "number",
|
|
2667
|
+
value: field.minLength === void 0 ? "" : String(field.minLength),
|
|
2668
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2669
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2670
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2671
|
+
const parsed = value.trim().length === 0 ? void 0 : Number(value);
|
|
2672
|
+
return parsed === void 0 || !Number.isInteger(parsed) || parsed < 0 ? current : { ...current, minLength: parsed };
|
|
2673
|
+
})
|
|
2674
|
+
}
|
|
2675
|
+
) }),
|
|
2676
|
+
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2677
|
+
TextInput,
|
|
2678
|
+
{
|
|
2679
|
+
id: `builder-field-${field.id}-max-length`,
|
|
2680
|
+
label: translate("builder.maximumLength"),
|
|
2681
|
+
type: "number",
|
|
2682
|
+
value: field.maxLength === void 0 ? "" : String(field.maxLength),
|
|
2683
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2684
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2685
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2686
|
+
const parsed = value.trim().length === 0 ? void 0 : Number(value);
|
|
2687
|
+
return parsed === void 0 || !Number.isInteger(parsed) || parsed < 0 ? current : { ...current, maxLength: parsed };
|
|
2688
|
+
})
|
|
2689
|
+
}
|
|
2690
|
+
) }),
|
|
2691
|
+
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2692
|
+
TextInput,
|
|
2693
|
+
{
|
|
2694
|
+
id: `builder-field-${field.id}-pattern`,
|
|
2695
|
+
label: translate("builder.pattern"),
|
|
2696
|
+
value: field.pattern ?? "",
|
|
2697
|
+
readOnly: controls.textLimits === "readOnly",
|
|
2698
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2699
|
+
if (current.type !== "text" && current.type !== "textarea") return current;
|
|
2700
|
+
return value.length === 0 ? current : { ...current, pattern: value };
|
|
2701
|
+
})
|
|
2702
|
+
}
|
|
2703
|
+
) })
|
|
2704
|
+
] }) : null,
|
|
2705
|
+
field.type === "number" && controls.numberLimits !== "hidden" ? /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__grid"), children: ["min", "max", "step"].map((property) => /* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2706
|
+
TextInput,
|
|
2707
|
+
{
|
|
2708
|
+
id: `builder-field-${field.id}-${property}`,
|
|
2709
|
+
label: translate(
|
|
2710
|
+
property === "step" ? "builder.step" : property === "min" ? "builder.minimum" : "builder.maximum"
|
|
2711
|
+
),
|
|
2712
|
+
type: "number",
|
|
2713
|
+
value: field[property] === void 0 ? "" : String(field[property]),
|
|
2714
|
+
readOnly: controls.numberLimits === "readOnly",
|
|
2715
|
+
onChange: (value) => updateField(field.id, (current) => {
|
|
2716
|
+
if (current.type !== "number") return current;
|
|
2717
|
+
if (value.trim().length === 0) {
|
|
2718
|
+
const { [property]: _removed, ...remaining } = current;
|
|
2719
|
+
return remaining;
|
|
2720
|
+
}
|
|
2721
|
+
const parsed = Number(value);
|
|
2722
|
+
return Number.isFinite(parsed) ? { ...current, [property]: parsed } : current;
|
|
2723
|
+
})
|
|
2724
|
+
}
|
|
2725
|
+
) }, property)) }) : null,
|
|
2726
|
+
"options" in field && controls.options !== "hidden" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__options"), children: [
|
|
2527
2727
|
/* @__PURE__ */ jsx("strong", { children: translate("builder.options") }),
|
|
2528
2728
|
field.options.map(
|
|
2529
2729
|
(option, optionIndex) => OptionEditorSlot === void 0 ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__option"), children: [
|
|
@@ -2534,6 +2734,7 @@ function FormBuilder({
|
|
|
2534
2734
|
label: translate("builder.optionLabel", { index: optionIndex + 1 }),
|
|
2535
2735
|
value: option.label,
|
|
2536
2736
|
placeholder: translate("builder.optionLabelPlaceholder"),
|
|
2737
|
+
readOnly: controls.options === "readOnly",
|
|
2537
2738
|
onChange: (value) => value.trim().length === 0 ? void 0 : updateOption(field.id, option.id, value)
|
|
2538
2739
|
}
|
|
2539
2740
|
),
|
|
@@ -2543,7 +2744,7 @@ function FormBuilder({
|
|
|
2543
2744
|
{
|
|
2544
2745
|
actionType: "moveUp",
|
|
2545
2746
|
title: translate("builder.moveUp", { title: option.label }),
|
|
2546
|
-
disabled: optionIndex === 0,
|
|
2747
|
+
disabled: controls.options === "readOnly" || optionIndex === 0,
|
|
2547
2748
|
onClick: () => moveOption(field.id, option.id, optionIndex - 1)
|
|
2548
2749
|
}
|
|
2549
2750
|
),
|
|
@@ -2552,7 +2753,7 @@ function FormBuilder({
|
|
|
2552
2753
|
{
|
|
2553
2754
|
actionType: "moveDown",
|
|
2554
2755
|
title: translate("builder.moveDown", { title: option.label }),
|
|
2555
|
-
disabled: optionIndex === field.options.length - 1,
|
|
2756
|
+
disabled: controls.options === "readOnly" || optionIndex === field.options.length - 1,
|
|
2556
2757
|
onClick: () => moveOption(field.id, option.id, optionIndex + 1)
|
|
2557
2758
|
}
|
|
2558
2759
|
),
|
|
@@ -2560,7 +2761,7 @@ function FormBuilder({
|
|
|
2560
2761
|
IconButton,
|
|
2561
2762
|
{
|
|
2562
2763
|
actionType: "delete",
|
|
2563
|
-
disabled: field.options.length === 1,
|
|
2764
|
+
disabled: controls.options === "readOnly" || field.options.length === 1,
|
|
2564
2765
|
onClick: () => removeOption(field.id, option.id),
|
|
2565
2766
|
title: translate("builder.remove")
|
|
2566
2767
|
}
|
|
@@ -2578,7 +2779,7 @@ function FormBuilder({
|
|
|
2578
2779
|
onMoveUp: () => moveOption(field.id, option.id, optionIndex - 1),
|
|
2579
2780
|
onMoveDown: () => moveOption(field.id, option.id, optionIndex + 1),
|
|
2580
2781
|
onRemove: () => removeOption(field.id, option.id),
|
|
2581
|
-
readOnly,
|
|
2782
|
+
readOnly: readOnly || controls.options === "readOnly",
|
|
2582
2783
|
actions,
|
|
2583
2784
|
components
|
|
2584
2785
|
}
|
|
@@ -2592,7 +2793,7 @@ function FormBuilder({
|
|
|
2592
2793
|
index: optionIndex,
|
|
2593
2794
|
currentLocale: editingLocale,
|
|
2594
2795
|
translate,
|
|
2595
|
-
readOnly,
|
|
2796
|
+
readOnly: readOnly || controls.options === "readOnly",
|
|
2596
2797
|
actions,
|
|
2597
2798
|
components
|
|
2598
2799
|
},
|
|
@@ -2604,13 +2805,13 @@ function FormBuilder({
|
|
|
2604
2805
|
{
|
|
2605
2806
|
action: "addOption",
|
|
2606
2807
|
targetId: field.id,
|
|
2607
|
-
disabled: policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
2808
|
+
disabled: controls.options === "readOnly" || policy?.maxOptionsPerField !== void 0 && field.options.length >= policy.maxOptionsPerField,
|
|
2608
2809
|
onClick: () => addOption(field.id),
|
|
2609
2810
|
children: translate("builder.addOption")
|
|
2610
2811
|
}
|
|
2611
2812
|
)
|
|
2612
2813
|
] }) : null,
|
|
2613
|
-
conditionsEnabled ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__condition"), children: [
|
|
2814
|
+
conditionsEnabled && controls.displayConditions !== "hidden" ? /* @__PURE__ */ jsxs("div", { className: builderClass("form-engine-builder__condition"), children: [
|
|
2614
2815
|
/* @__PURE__ */ jsx("div", { className: builderClass("form-engine-builder__field"), children: /* @__PURE__ */ jsx(
|
|
2615
2816
|
Select,
|
|
2616
2817
|
{
|
|
@@ -3985,6 +4186,8 @@ export {
|
|
|
3985
4186
|
createLocalStorageSubmissionAttemptStore,
|
|
3986
4187
|
createLocalStorageSubmissionReceiptStore,
|
|
3987
4188
|
isTranslationUnresolved,
|
|
4189
|
+
resolveFieldEditorControls,
|
|
4190
|
+
resolveFieldTypeSelectOptions,
|
|
3988
4191
|
resolveInitialFieldType,
|
|
3989
4192
|
resolveTranslation,
|
|
3990
4193
|
submissionReceiptQueryKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@form-engine-ts/core": "4.
|
|
46
|
-
"@form-engine-ts/privacy": "4.
|
|
45
|
+
"@form-engine-ts/core": "4.2.0",
|
|
46
|
+
"@form-engine-ts/privacy": "4.2.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|