@dmitryvim/form-builder 0.2.9 → 0.2.11
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 +104 -40
- package/dist/browser/formbuilder.min.js +51 -51
- package/dist/browser/formbuilder.v0.2.11.min.js +322 -0
- package/dist/cjs/index.cjs +542 -138
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +534 -134
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +51 -51
- package/dist/types/instance/FormBuilderInstance.d.ts +5 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +6 -3
- package/dist/types/utils/enable-conditions.d.ts +18 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.9.min.js +0 -322
- package/dist/types/utils/display-conditions.d.ts +0 -17
package/dist/esm/index.js
CHANGED
|
@@ -60,6 +60,41 @@ function validateSchema(schema) {
|
|
|
60
60
|
errors.push("Schema missing elements array");
|
|
61
61
|
return errors;
|
|
62
62
|
}
|
|
63
|
+
if ("columns" in schema && schema.columns !== void 0) {
|
|
64
|
+
const columns = schema.columns;
|
|
65
|
+
const validColumns = [1, 2, 3, 4];
|
|
66
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
67
|
+
errors.push(`schema.columns must be 1, 2, 3, or 4 (got ${columns})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if ("prefillHints" in schema && schema.prefillHints) {
|
|
71
|
+
const prefillHints = schema.prefillHints;
|
|
72
|
+
if (Array.isArray(prefillHints)) {
|
|
73
|
+
prefillHints.forEach((hint, hintIndex) => {
|
|
74
|
+
if (!hint.label || typeof hint.label !== "string") {
|
|
75
|
+
errors.push(
|
|
76
|
+
`schema.prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!hint.values || typeof hint.values !== "object") {
|
|
80
|
+
errors.push(
|
|
81
|
+
`schema.prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
for (const fieldKey in hint.values) {
|
|
85
|
+
const fieldExists = schema.elements.some(
|
|
86
|
+
(element) => element.key === fieldKey
|
|
87
|
+
);
|
|
88
|
+
if (!fieldExists) {
|
|
89
|
+
errors.push(
|
|
90
|
+
`schema.prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
63
98
|
function validateElements(elements, path) {
|
|
64
99
|
elements.forEach((element, index) => {
|
|
65
100
|
const elementPath = `${path}[${index}]`;
|
|
@@ -69,17 +104,17 @@ function validateSchema(schema) {
|
|
|
69
104
|
if (!element.key) {
|
|
70
105
|
errors.push(`${elementPath}: missing key`);
|
|
71
106
|
}
|
|
72
|
-
if (element.
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
107
|
+
if (element.enableIf) {
|
|
108
|
+
const enableIf = element.enableIf;
|
|
109
|
+
if (!enableIf.key || typeof enableIf.key !== "string") {
|
|
75
110
|
errors.push(
|
|
76
|
-
`${elementPath}:
|
|
111
|
+
`${elementPath}: enableIf must have a 'key' property of type string`
|
|
77
112
|
);
|
|
78
113
|
}
|
|
79
|
-
const hasOperator = "equals" in
|
|
114
|
+
const hasOperator = "equals" in enableIf;
|
|
80
115
|
if (!hasOperator) {
|
|
81
116
|
errors.push(
|
|
82
|
-
`${elementPath}:
|
|
117
|
+
`${elementPath}: enableIf must have at least one operator (equals, etc.)`
|
|
83
118
|
);
|
|
84
119
|
}
|
|
85
120
|
}
|
|
@@ -157,7 +192,7 @@ function clear(node) {
|
|
|
157
192
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
158
193
|
}
|
|
159
194
|
|
|
160
|
-
// src/utils/
|
|
195
|
+
// src/utils/enable-conditions.ts
|
|
161
196
|
function getValueByPath(data, path) {
|
|
162
197
|
if (!data || typeof data !== "object") {
|
|
163
198
|
return void 0;
|
|
@@ -183,18 +218,27 @@ function getValueByPath(data, path) {
|
|
|
183
218
|
}
|
|
184
219
|
return current;
|
|
185
220
|
}
|
|
186
|
-
function
|
|
221
|
+
function evaluateEnableCondition(condition, formData, containerData) {
|
|
187
222
|
if (!condition || !condition.key) {
|
|
223
|
+
throw new Error("Invalid enableIf condition: must have a 'key' property");
|
|
224
|
+
}
|
|
225
|
+
const scope = condition.scope ?? "relative";
|
|
226
|
+
let dataSource;
|
|
227
|
+
if (scope === "relative") {
|
|
228
|
+
dataSource = containerData ?? formData;
|
|
229
|
+
} else if (scope === "absolute") {
|
|
230
|
+
dataSource = formData;
|
|
231
|
+
} else {
|
|
188
232
|
throw new Error(
|
|
189
|
-
|
|
233
|
+
`Invalid enableIf scope: must be "relative" or "absolute" (got "${scope}")`
|
|
190
234
|
);
|
|
191
235
|
}
|
|
192
|
-
const actualValue = getValueByPath(
|
|
236
|
+
const actualValue = getValueByPath(dataSource, condition.key);
|
|
193
237
|
if ("equals" in condition) {
|
|
194
238
|
return deepEqual(actualValue, condition.equals);
|
|
195
239
|
}
|
|
196
240
|
throw new Error(
|
|
197
|
-
`Invalid
|
|
241
|
+
`Invalid enableIf condition: no recognized operator (equals, etc.)`
|
|
198
242
|
);
|
|
199
243
|
}
|
|
200
244
|
function deepEqual(a, b) {
|
|
@@ -207,7 +251,7 @@ function deepEqual(a, b) {
|
|
|
207
251
|
} catch (e) {
|
|
208
252
|
if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
|
|
209
253
|
console.warn(
|
|
210
|
-
"deepEqual: Circular reference detected in
|
|
254
|
+
"deepEqual: Circular reference detected in enableIf comparison, using reference equality"
|
|
211
255
|
);
|
|
212
256
|
return a === b;
|
|
213
257
|
}
|
|
@@ -260,7 +304,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
260
304
|
}
|
|
261
305
|
if (!state.config.readonly && ctx.instance) {
|
|
262
306
|
const handleChange = () => {
|
|
263
|
-
|
|
307
|
+
const value = textInput.value === "" ? null : textInput.value;
|
|
308
|
+
ctx.instance.triggerOnChange(pathKey, value);
|
|
264
309
|
};
|
|
265
310
|
textInput.addEventListener("blur", handleChange);
|
|
266
311
|
textInput.addEventListener("input", handleChange);
|
|
@@ -338,7 +383,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
338
383
|
}
|
|
339
384
|
if (!state.config.readonly && ctx.instance) {
|
|
340
385
|
const handleChange = () => {
|
|
341
|
-
|
|
386
|
+
const value2 = textInput.value === "" ? null : textInput.value;
|
|
387
|
+
ctx.instance.triggerOnChange(textInput.name, value2);
|
|
342
388
|
};
|
|
343
389
|
textInput.addEventListener("blur", handleChange);
|
|
344
390
|
textInput.addEventListener("input", handleChange);
|
|
@@ -504,19 +550,19 @@ function validateTextElement(element, key, context) {
|
|
|
504
550
|
}
|
|
505
551
|
};
|
|
506
552
|
if (element.multiple) {
|
|
507
|
-
const inputs = scopeRoot.querySelectorAll(
|
|
508
|
-
`[name^="${key}["]`
|
|
509
|
-
);
|
|
553
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${key}["]`);
|
|
510
554
|
const values = [];
|
|
555
|
+
const rawValues = [];
|
|
511
556
|
inputs.forEach((input, index) => {
|
|
512
557
|
const val = input?.value ?? "";
|
|
513
|
-
|
|
558
|
+
rawValues.push(val);
|
|
559
|
+
values.push(val === "" ? null : val);
|
|
514
560
|
validateTextInput(input, val, `${key}[${index}]`);
|
|
515
561
|
});
|
|
516
562
|
if (!skipValidation) {
|
|
517
563
|
const minCount = element.minCount ?? 1;
|
|
518
564
|
const maxCount = element.maxCount ?? Infinity;
|
|
519
|
-
const filteredValues =
|
|
565
|
+
const filteredValues = rawValues.filter((v) => v.trim() !== "");
|
|
520
566
|
if (element.required && filteredValues.length === 0) {
|
|
521
567
|
errors.push(`${key}: required`);
|
|
522
568
|
}
|
|
@@ -529,17 +575,17 @@ function validateTextElement(element, key, context) {
|
|
|
529
575
|
}
|
|
530
576
|
return { value: values, errors };
|
|
531
577
|
} else {
|
|
532
|
-
const input = scopeRoot.querySelector(
|
|
533
|
-
`[name$="${key}"]`
|
|
534
|
-
);
|
|
578
|
+
const input = scopeRoot.querySelector(`[name$="${key}"]`);
|
|
535
579
|
const val = input?.value ?? "";
|
|
536
580
|
if (!skipValidation && element.required && val === "") {
|
|
537
581
|
errors.push(`${key}: required`);
|
|
538
582
|
markValidity(input, "required");
|
|
539
|
-
return { value:
|
|
583
|
+
return { value: null, errors };
|
|
540
584
|
}
|
|
541
|
-
|
|
542
|
-
|
|
585
|
+
if (input) {
|
|
586
|
+
validateTextInput(input, val, key);
|
|
587
|
+
}
|
|
588
|
+
return { value: val === "" ? null : val, errors };
|
|
543
589
|
}
|
|
544
590
|
}
|
|
545
591
|
function updateTextField(element, fieldPath, value, context) {
|
|
@@ -551,9 +597,7 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
551
597
|
);
|
|
552
598
|
return;
|
|
553
599
|
}
|
|
554
|
-
const inputs = scopeRoot.querySelectorAll(
|
|
555
|
-
`[name^="${fieldPath}["]`
|
|
556
|
-
);
|
|
600
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["]`);
|
|
557
601
|
inputs.forEach((input, index) => {
|
|
558
602
|
if (index < value.length) {
|
|
559
603
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
@@ -567,9 +611,7 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
567
611
|
);
|
|
568
612
|
}
|
|
569
613
|
} else {
|
|
570
|
-
const input = scopeRoot.querySelector(
|
|
571
|
-
`[name="${fieldPath}"]`
|
|
572
|
-
);
|
|
614
|
+
const input = scopeRoot.querySelector(`[name="${fieldPath}"]`);
|
|
573
615
|
if (input) {
|
|
574
616
|
input.value = value != null ? String(value) : "";
|
|
575
617
|
input.classList.remove("invalid");
|
|
@@ -590,7 +632,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
590
632
|
textareaInput.readOnly = state.config.readonly;
|
|
591
633
|
if (!state.config.readonly && ctx.instance) {
|
|
592
634
|
const handleChange = () => {
|
|
593
|
-
|
|
635
|
+
const value = textareaInput.value === "" ? null : textareaInput.value;
|
|
636
|
+
ctx.instance.triggerOnChange(pathKey, value);
|
|
594
637
|
};
|
|
595
638
|
textareaInput.addEventListener("blur", handleChange);
|
|
596
639
|
textareaInput.addEventListener("input", handleChange);
|
|
@@ -633,7 +676,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
633
676
|
textareaInput.readOnly = state.config.readonly;
|
|
634
677
|
if (!state.config.readonly && ctx.instance) {
|
|
635
678
|
const handleChange = () => {
|
|
636
|
-
|
|
679
|
+
const value2 = textareaInput.value === "" ? null : textareaInput.value;
|
|
680
|
+
ctx.instance.triggerOnChange(textareaInput.name, value2);
|
|
637
681
|
};
|
|
638
682
|
textareaInput.addEventListener("blur", handleChange);
|
|
639
683
|
textareaInput.addEventListener("input", handleChange);
|
|
@@ -1297,7 +1341,9 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
1297
1341
|
return newContainer;
|
|
1298
1342
|
}
|
|
1299
1343
|
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
1300
|
-
const changeBtn = container.querySelector(
|
|
1344
|
+
const changeBtn = container.querySelector(
|
|
1345
|
+
".change-file-btn"
|
|
1346
|
+
);
|
|
1301
1347
|
if (changeBtn) {
|
|
1302
1348
|
changeBtn.onclick = (e) => {
|
|
1303
1349
|
e.stopPropagation();
|
|
@@ -1306,7 +1352,9 @@ function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
|
1306
1352
|
}
|
|
1307
1353
|
};
|
|
1308
1354
|
}
|
|
1309
|
-
const deleteBtn = container.querySelector(
|
|
1355
|
+
const deleteBtn = container.querySelector(
|
|
1356
|
+
".delete-file-btn"
|
|
1357
|
+
);
|
|
1310
1358
|
if (deleteBtn) {
|
|
1311
1359
|
deleteBtn.onclick = (e) => {
|
|
1312
1360
|
e.stopPropagation();
|
|
@@ -1347,7 +1395,9 @@ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
|
1347
1395
|
source.src = thumbnailUrl;
|
|
1348
1396
|
source.type = videoType;
|
|
1349
1397
|
video.appendChild(source);
|
|
1350
|
-
video.appendChild(
|
|
1398
|
+
video.appendChild(
|
|
1399
|
+
document.createTextNode("Your browser does not support the video tag.")
|
|
1400
|
+
);
|
|
1351
1401
|
container.appendChild(video);
|
|
1352
1402
|
}
|
|
1353
1403
|
function renderDeleteButton(container, resourceId, state) {
|
|
@@ -1448,7 +1498,13 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
1448
1498
|
deps
|
|
1449
1499
|
);
|
|
1450
1500
|
} else {
|
|
1451
|
-
await renderUploadedFilePreview(
|
|
1501
|
+
await renderUploadedFilePreview(
|
|
1502
|
+
container,
|
|
1503
|
+
resourceId,
|
|
1504
|
+
fileName,
|
|
1505
|
+
meta,
|
|
1506
|
+
state
|
|
1507
|
+
);
|
|
1452
1508
|
}
|
|
1453
1509
|
}
|
|
1454
1510
|
async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
@@ -1606,7 +1662,9 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1606
1662
|
if (fileInput) fileInput.click();
|
|
1607
1663
|
};
|
|
1608
1664
|
textContainer.appendChild(uploadLink);
|
|
1609
|
-
textContainer.appendChild(
|
|
1665
|
+
textContainer.appendChild(
|
|
1666
|
+
document.createTextNode(` ${t("dragDropText", state)}`)
|
|
1667
|
+
);
|
|
1610
1668
|
container.appendChild(gridContainer);
|
|
1611
1669
|
container.appendChild(textContainer);
|
|
1612
1670
|
return;
|
|
@@ -2069,7 +2127,14 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2069
2127
|
const dragHandler = (files) => {
|
|
2070
2128
|
if (files.length > 0) {
|
|
2071
2129
|
const deps = { picker, fileUploadHandler, dragHandler };
|
|
2072
|
-
handleFileSelect(
|
|
2130
|
+
handleFileSelect(
|
|
2131
|
+
files[0],
|
|
2132
|
+
fileContainer,
|
|
2133
|
+
pathKey,
|
|
2134
|
+
state,
|
|
2135
|
+
deps,
|
|
2136
|
+
ctx.instance
|
|
2137
|
+
);
|
|
2073
2138
|
}
|
|
2074
2139
|
};
|
|
2075
2140
|
if (initial) {
|
|
@@ -2093,7 +2158,14 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2093
2158
|
picker.onchange = () => {
|
|
2094
2159
|
if (picker.files && picker.files.length > 0) {
|
|
2095
2160
|
const deps = { picker, fileUploadHandler, dragHandler };
|
|
2096
|
-
handleFileSelect(
|
|
2161
|
+
handleFileSelect(
|
|
2162
|
+
picker.files[0],
|
|
2163
|
+
fileContainer,
|
|
2164
|
+
pathKey,
|
|
2165
|
+
state,
|
|
2166
|
+
deps,
|
|
2167
|
+
ctx.instance
|
|
2168
|
+
);
|
|
2097
2169
|
}
|
|
2098
2170
|
};
|
|
2099
2171
|
fileWrapper.appendChild(fileContainer);
|
|
@@ -2158,8 +2230,22 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2158
2230
|
const initialFiles = ctx.prefill[element.key] || [];
|
|
2159
2231
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2160
2232
|
updateFilesList2();
|
|
2161
|
-
setupFilesDropHandler(
|
|
2162
|
-
|
|
2233
|
+
setupFilesDropHandler(
|
|
2234
|
+
filesContainer,
|
|
2235
|
+
initialFiles,
|
|
2236
|
+
state,
|
|
2237
|
+
updateFilesList2,
|
|
2238
|
+
pathKey,
|
|
2239
|
+
ctx.instance
|
|
2240
|
+
);
|
|
2241
|
+
setupFilesPickerHandler(
|
|
2242
|
+
filesPicker,
|
|
2243
|
+
initialFiles,
|
|
2244
|
+
state,
|
|
2245
|
+
updateFilesList2,
|
|
2246
|
+
pathKey,
|
|
2247
|
+
ctx.instance
|
|
2248
|
+
);
|
|
2163
2249
|
filesContainer.appendChild(list);
|
|
2164
2250
|
filesWrapper.appendChild(filesContainer);
|
|
2165
2251
|
filesWrapper.appendChild(filesPicker);
|
|
@@ -2221,8 +2307,22 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2221
2307
|
if (existingCount) existingCount.remove();
|
|
2222
2308
|
filesWrapper.appendChild(countInfo);
|
|
2223
2309
|
};
|
|
2224
|
-
setupFilesDropHandler(
|
|
2225
|
-
|
|
2310
|
+
setupFilesDropHandler(
|
|
2311
|
+
filesContainer,
|
|
2312
|
+
initialFiles,
|
|
2313
|
+
state,
|
|
2314
|
+
updateFilesDisplay,
|
|
2315
|
+
pathKey,
|
|
2316
|
+
ctx.instance
|
|
2317
|
+
);
|
|
2318
|
+
setupFilesPickerHandler(
|
|
2319
|
+
filesPicker,
|
|
2320
|
+
initialFiles,
|
|
2321
|
+
state,
|
|
2322
|
+
updateFilesDisplay,
|
|
2323
|
+
pathKey,
|
|
2324
|
+
ctx.instance
|
|
2325
|
+
);
|
|
2226
2326
|
updateFilesDisplay();
|
|
2227
2327
|
wrapper.appendChild(filesWrapper);
|
|
2228
2328
|
}
|
|
@@ -2705,9 +2805,7 @@ function validateColourElement(element, key, context) {
|
|
|
2705
2805
|
return normalized;
|
|
2706
2806
|
};
|
|
2707
2807
|
if (element.multiple) {
|
|
2708
|
-
const hexInputs = scopeRoot.querySelectorAll(
|
|
2709
|
-
`.colour-hex-input`
|
|
2710
|
-
);
|
|
2808
|
+
const hexInputs = scopeRoot.querySelectorAll(`[name^="${key}["].colour-hex-input`);
|
|
2711
2809
|
const values = [];
|
|
2712
2810
|
hexInputs.forEach((input, index) => {
|
|
2713
2811
|
const val = input?.value ?? "";
|
|
@@ -2752,9 +2850,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
2752
2850
|
);
|
|
2753
2851
|
return;
|
|
2754
2852
|
}
|
|
2755
|
-
const hexInputs = scopeRoot.querySelectorAll(
|
|
2756
|
-
`.colour-hex-input`
|
|
2757
|
-
);
|
|
2853
|
+
const hexInputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["].colour-hex-input`);
|
|
2758
2854
|
hexInputs.forEach((hexInput, index) => {
|
|
2759
2855
|
if (index < value.length) {
|
|
2760
2856
|
const normalized = normalizeColourValue(value[index]);
|
|
@@ -2764,7 +2860,9 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
2764
2860
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
2765
2861
|
if (wrapper) {
|
|
2766
2862
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
2767
|
-
const colourInput = wrapper.querySelector(
|
|
2863
|
+
const colourInput = wrapper.querySelector(
|
|
2864
|
+
".colour-picker-hidden"
|
|
2865
|
+
);
|
|
2768
2866
|
if (swatch) {
|
|
2769
2867
|
swatch.style.backgroundColor = normalized;
|
|
2770
2868
|
}
|
|
@@ -2791,7 +2889,9 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
2791
2889
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
2792
2890
|
if (wrapper) {
|
|
2793
2891
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
2794
|
-
const colourInput = wrapper.querySelector(
|
|
2892
|
+
const colourInput = wrapper.querySelector(
|
|
2893
|
+
".colour-picker-hidden"
|
|
2894
|
+
);
|
|
2795
2895
|
if (swatch) {
|
|
2796
2896
|
swatch.style.backgroundColor = normalized;
|
|
2797
2897
|
}
|
|
@@ -2929,14 +3029,10 @@ function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
|
2929
3029
|
}
|
|
2930
3030
|
function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
2931
3031
|
if (element.min === void 0 || element.min === null) {
|
|
2932
|
-
throw new Error(
|
|
2933
|
-
`Slider field "${element.key}" requires "min" property`
|
|
2934
|
-
);
|
|
3032
|
+
throw new Error(`Slider field "${element.key}" requires "min" property`);
|
|
2935
3033
|
}
|
|
2936
3034
|
if (element.max === void 0 || element.max === null) {
|
|
2937
|
-
throw new Error(
|
|
2938
|
-
`Slider field "${element.key}" requires "max" property`
|
|
2939
|
-
);
|
|
3035
|
+
throw new Error(`Slider field "${element.key}" requires "max" property`);
|
|
2940
3036
|
}
|
|
2941
3037
|
if (element.min >= element.max) {
|
|
2942
3038
|
throw new Error(
|
|
@@ -2965,14 +3061,10 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
2965
3061
|
}
|
|
2966
3062
|
function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
2967
3063
|
if (element.min === void 0 || element.min === null) {
|
|
2968
|
-
throw new Error(
|
|
2969
|
-
`Slider field "${element.key}" requires "min" property`
|
|
2970
|
-
);
|
|
3064
|
+
throw new Error(`Slider field "${element.key}" requires "min" property`);
|
|
2971
3065
|
}
|
|
2972
3066
|
if (element.max === void 0 || element.max === null) {
|
|
2973
|
-
throw new Error(
|
|
2974
|
-
`Slider field "${element.key}" requires "max" property`
|
|
2975
|
-
);
|
|
3067
|
+
throw new Error(`Slider field "${element.key}" requires "max" property`);
|
|
2976
3068
|
}
|
|
2977
3069
|
if (element.min >= element.max) {
|
|
2978
3070
|
throw new Error(
|
|
@@ -3143,7 +3235,10 @@ function validateSliderElement(element, key, context) {
|
|
|
3143
3235
|
`;
|
|
3144
3236
|
const sliderContainer = input.closest(".slider-container");
|
|
3145
3237
|
if (sliderContainer && sliderContainer.nextSibling) {
|
|
3146
|
-
sliderContainer.parentNode?.insertBefore(
|
|
3238
|
+
sliderContainer.parentNode?.insertBefore(
|
|
3239
|
+
errorElement,
|
|
3240
|
+
sliderContainer.nextSibling
|
|
3241
|
+
);
|
|
3147
3242
|
} else if (sliderContainer) {
|
|
3148
3243
|
sliderContainer.parentNode?.appendChild(errorElement);
|
|
3149
3244
|
}
|
|
@@ -3314,6 +3409,33 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
3314
3409
|
}
|
|
3315
3410
|
|
|
3316
3411
|
// src/components/container.ts
|
|
3412
|
+
function extractRootFormData(formRoot) {
|
|
3413
|
+
const data = {};
|
|
3414
|
+
const inputs = formRoot.querySelectorAll(
|
|
3415
|
+
"input, select, textarea"
|
|
3416
|
+
);
|
|
3417
|
+
inputs.forEach((input) => {
|
|
3418
|
+
const fieldName = input.getAttribute("name");
|
|
3419
|
+
if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
|
|
3420
|
+
if (input instanceof HTMLSelectElement) {
|
|
3421
|
+
data[fieldName] = input.value;
|
|
3422
|
+
} else if (input instanceof HTMLInputElement) {
|
|
3423
|
+
if (input.type === "checkbox") {
|
|
3424
|
+
data[fieldName] = input.checked;
|
|
3425
|
+
} else if (input.type === "radio") {
|
|
3426
|
+
if (input.checked) {
|
|
3427
|
+
data[fieldName] = input.value;
|
|
3428
|
+
}
|
|
3429
|
+
} else {
|
|
3430
|
+
data[fieldName] = input.value;
|
|
3431
|
+
}
|
|
3432
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
3433
|
+
data[fieldName] = input.value;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
});
|
|
3437
|
+
return data;
|
|
3438
|
+
}
|
|
3317
3439
|
var renderElementFunc = null;
|
|
3318
3440
|
function setRenderElement(fn) {
|
|
3319
3441
|
renderElementFunc = fn;
|
|
@@ -3372,7 +3494,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3372
3494
|
prefill: ctx.prefill?.[element.key] || {},
|
|
3373
3495
|
// Sliced data for value population
|
|
3374
3496
|
formData: ctx.formData ?? ctx.prefill,
|
|
3375
|
-
// Complete root data for
|
|
3497
|
+
// Complete root data for enableIf evaluation
|
|
3376
3498
|
state: ctx.state
|
|
3377
3499
|
};
|
|
3378
3500
|
element.elements.forEach((child) => {
|
|
@@ -3427,12 +3549,13 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3427
3549
|
add.onclick = () => {
|
|
3428
3550
|
if (countItems() < max) {
|
|
3429
3551
|
const idx = countItems();
|
|
3552
|
+
const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
|
|
3430
3553
|
const subCtx = {
|
|
3431
3554
|
state: ctx.state,
|
|
3432
3555
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
3433
3556
|
prefill: {},
|
|
3434
|
-
formData:
|
|
3435
|
-
//
|
|
3557
|
+
formData: currentFormData
|
|
3558
|
+
// Current root data from DOM for enableIf
|
|
3436
3559
|
};
|
|
3437
3560
|
const item = document.createElement("div");
|
|
3438
3561
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
@@ -3481,7 +3604,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3481
3604
|
};
|
|
3482
3605
|
const updateAddButton = () => {
|
|
3483
3606
|
const currentCount = countItems();
|
|
3484
|
-
const existingAddBtn = containerWrap.querySelector(
|
|
3607
|
+
const existingAddBtn = containerWrap.querySelector(
|
|
3608
|
+
".add-container-btn"
|
|
3609
|
+
);
|
|
3485
3610
|
if (existingAddBtn) {
|
|
3486
3611
|
existingAddBtn.disabled = currentCount >= max;
|
|
3487
3612
|
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
@@ -3496,7 +3621,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3496
3621
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
3497
3622
|
prefill: prefillObj || {},
|
|
3498
3623
|
formData: ctx.formData ?? ctx.prefill
|
|
3499
|
-
// Complete root data for
|
|
3624
|
+
// Complete root data for enableIf
|
|
3500
3625
|
};
|
|
3501
3626
|
const item = document.createElement("div");
|
|
3502
3627
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
@@ -3548,7 +3673,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3548
3673
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
3549
3674
|
prefill: {},
|
|
3550
3675
|
formData: ctx.formData ?? ctx.prefill
|
|
3551
|
-
// Complete root data for
|
|
3676
|
+
// Complete root data for enableIf
|
|
3552
3677
|
};
|
|
3553
3678
|
const item = document.createElement("div");
|
|
3554
3679
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
@@ -3647,6 +3772,26 @@ function validateContainerElement(element, key, context) {
|
|
|
3647
3772
|
`[data-container-item="${key}[${i}]"]`
|
|
3648
3773
|
) || scopeRoot;
|
|
3649
3774
|
element.elements.forEach((child) => {
|
|
3775
|
+
if (child.enableIf) {
|
|
3776
|
+
try {
|
|
3777
|
+
const rootFormData = context.instance?.getState().formRoot ? extractRootFormData(context.instance.getState().formRoot) : {};
|
|
3778
|
+
const shouldEnable = evaluateEnableCondition(
|
|
3779
|
+
child.enableIf,
|
|
3780
|
+
rootFormData,
|
|
3781
|
+
// Root form data for absolute scope
|
|
3782
|
+
itemData
|
|
3783
|
+
// Container data for relative scope
|
|
3784
|
+
);
|
|
3785
|
+
if (!shouldEnable) {
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
} catch (error) {
|
|
3789
|
+
console.error(
|
|
3790
|
+
`Error evaluating enableIf for field "${child.key}" in container "${key}[${i}]":`,
|
|
3791
|
+
error
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3650
3795
|
if (child.hidden || child.type === "hidden") {
|
|
3651
3796
|
itemData[child.key] = child.default !== void 0 ? child.default : null;
|
|
3652
3797
|
} else {
|
|
@@ -3666,6 +3811,26 @@ function validateContainerElement(element, key, context) {
|
|
|
3666
3811
|
const containerData = {};
|
|
3667
3812
|
const containerContainer = scopeRoot.querySelector(`[data-container="${key}"]`) || scopeRoot;
|
|
3668
3813
|
element.elements.forEach((child) => {
|
|
3814
|
+
if (child.enableIf) {
|
|
3815
|
+
try {
|
|
3816
|
+
const rootFormData = context.instance?.getState().formRoot ? extractRootFormData(context.instance.getState().formRoot) : {};
|
|
3817
|
+
const shouldEnable = evaluateEnableCondition(
|
|
3818
|
+
child.enableIf,
|
|
3819
|
+
rootFormData,
|
|
3820
|
+
// Root form data for absolute scope
|
|
3821
|
+
containerData
|
|
3822
|
+
// Container data for relative scope
|
|
3823
|
+
);
|
|
3824
|
+
if (!shouldEnable) {
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
} catch (error) {
|
|
3828
|
+
console.error(
|
|
3829
|
+
`Error evaluating enableIf for field "${child.key}" in container "${key}":`,
|
|
3830
|
+
error
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3669
3834
|
if (child.hidden || child.type === "hidden") {
|
|
3670
3835
|
containerData[child.key] = child.default !== void 0 ? child.default : null;
|
|
3671
3836
|
} else {
|
|
@@ -3850,31 +4015,180 @@ if (typeof document !== "undefined") {
|
|
|
3850
4015
|
}
|
|
3851
4016
|
});
|
|
3852
4017
|
}
|
|
3853
|
-
function
|
|
3854
|
-
if (!element.
|
|
3855
|
-
return
|
|
4018
|
+
function shouldDisableElement(element, ctx) {
|
|
4019
|
+
if (!element.enableIf) {
|
|
4020
|
+
return false;
|
|
3856
4021
|
}
|
|
3857
4022
|
try {
|
|
3858
|
-
const
|
|
3859
|
-
const
|
|
3860
|
-
|
|
3861
|
-
|
|
4023
|
+
const rootFormData = ctx.formData ?? ctx.prefill ?? {};
|
|
4024
|
+
const containerData = ctx.path ? getValueByPath(rootFormData, ctx.path) : void 0;
|
|
4025
|
+
const shouldEnable = evaluateEnableCondition(
|
|
4026
|
+
element.enableIf,
|
|
4027
|
+
rootFormData,
|
|
4028
|
+
containerData
|
|
3862
4029
|
);
|
|
3863
|
-
|
|
3864
|
-
const hiddenWrapper = document.createElement("div");
|
|
3865
|
-
hiddenWrapper.className = "fb-field-wrapper-hidden";
|
|
3866
|
-
hiddenWrapper.style.display = "none";
|
|
3867
|
-
hiddenWrapper.setAttribute("data-field-key", element.key);
|
|
3868
|
-
hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
|
|
3869
|
-
return hiddenWrapper;
|
|
3870
|
-
}
|
|
4030
|
+
return !shouldEnable;
|
|
3871
4031
|
} catch (error) {
|
|
3872
4032
|
console.error(
|
|
3873
|
-
`Error evaluating
|
|
4033
|
+
`Error evaluating enableIf for field "${element.key}":`,
|
|
3874
4034
|
error
|
|
3875
4035
|
);
|
|
3876
4036
|
}
|
|
3877
|
-
return
|
|
4037
|
+
return false;
|
|
4038
|
+
}
|
|
4039
|
+
function extractDOMValue(fieldPath, formRoot) {
|
|
4040
|
+
const input = formRoot.querySelector(
|
|
4041
|
+
`[name="${fieldPath}"]`
|
|
4042
|
+
);
|
|
4043
|
+
if (!input) {
|
|
4044
|
+
return void 0;
|
|
4045
|
+
}
|
|
4046
|
+
if (input instanceof HTMLSelectElement) {
|
|
4047
|
+
return input.value;
|
|
4048
|
+
} else if (input instanceof HTMLInputElement) {
|
|
4049
|
+
if (input.type === "checkbox") {
|
|
4050
|
+
return input.checked;
|
|
4051
|
+
} else if (input.type === "radio") {
|
|
4052
|
+
const checked = formRoot.querySelector(
|
|
4053
|
+
`[name="${fieldPath}"]:checked`
|
|
4054
|
+
);
|
|
4055
|
+
return checked ? checked.value : void 0;
|
|
4056
|
+
} else {
|
|
4057
|
+
return input.value;
|
|
4058
|
+
}
|
|
4059
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
4060
|
+
return input.value;
|
|
4061
|
+
}
|
|
4062
|
+
return void 0;
|
|
4063
|
+
}
|
|
4064
|
+
function reevaluateEnableIf(wrapper, element, ctx) {
|
|
4065
|
+
if (!element.enableIf) {
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
const formRoot = ctx.state.formRoot;
|
|
4069
|
+
if (!formRoot) {
|
|
4070
|
+
console.error(`Cannot re-evaluate enableIf: formRoot is null`);
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
const condition = element.enableIf;
|
|
4074
|
+
const scope = condition.scope ?? "relative";
|
|
4075
|
+
let rootFormData = {};
|
|
4076
|
+
const containerData = {};
|
|
4077
|
+
const effectiveScope = !ctx.path || ctx.path === "" ? "absolute" : scope;
|
|
4078
|
+
if (effectiveScope === "relative" && ctx.path) {
|
|
4079
|
+
const containerMatch = ctx.path.match(/^(.+)\[(\d+)\]$/);
|
|
4080
|
+
if (containerMatch) {
|
|
4081
|
+
const containerKey = containerMatch[1];
|
|
4082
|
+
const containerIndex = parseInt(containerMatch[2], 10);
|
|
4083
|
+
const containerItemElement = formRoot.querySelector(
|
|
4084
|
+
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
4085
|
+
);
|
|
4086
|
+
if (containerItemElement) {
|
|
4087
|
+
const inputs = containerItemElement.querySelectorAll(
|
|
4088
|
+
"input, select, textarea"
|
|
4089
|
+
);
|
|
4090
|
+
inputs.forEach((input) => {
|
|
4091
|
+
const fieldName = input.getAttribute("name");
|
|
4092
|
+
if (fieldName) {
|
|
4093
|
+
const fieldKeyMatch = fieldName.match(/\.([^.[\]]+)$/);
|
|
4094
|
+
if (fieldKeyMatch) {
|
|
4095
|
+
const fieldKey = fieldKeyMatch[1];
|
|
4096
|
+
if (input instanceof HTMLSelectElement) {
|
|
4097
|
+
containerData[fieldKey] = input.value;
|
|
4098
|
+
} else if (input instanceof HTMLInputElement) {
|
|
4099
|
+
if (input.type === "checkbox") {
|
|
4100
|
+
containerData[fieldKey] = input.checked;
|
|
4101
|
+
} else if (input.type === "radio") {
|
|
4102
|
+
if (input.checked) {
|
|
4103
|
+
containerData[fieldKey] = input.value;
|
|
4104
|
+
}
|
|
4105
|
+
} else {
|
|
4106
|
+
containerData[fieldKey] = input.value;
|
|
4107
|
+
}
|
|
4108
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
4109
|
+
containerData[fieldKey] = input.value;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
} else {
|
|
4117
|
+
const dependencyKey = condition.key;
|
|
4118
|
+
const dependencyValue = extractDOMValue(dependencyKey, formRoot);
|
|
4119
|
+
if (dependencyValue !== void 0) {
|
|
4120
|
+
rootFormData[dependencyKey] = dependencyValue;
|
|
4121
|
+
} else {
|
|
4122
|
+
rootFormData = ctx.formData ?? ctx.prefill;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
try {
|
|
4126
|
+
const shouldEnable = evaluateEnableCondition(
|
|
4127
|
+
condition,
|
|
4128
|
+
rootFormData,
|
|
4129
|
+
containerData
|
|
4130
|
+
);
|
|
4131
|
+
if (shouldEnable) {
|
|
4132
|
+
wrapper.style.display = "";
|
|
4133
|
+
wrapper.classList.remove("fb-field-wrapper-disabled");
|
|
4134
|
+
wrapper.removeAttribute("data-conditionally-disabled");
|
|
4135
|
+
} else {
|
|
4136
|
+
wrapper.style.display = "none";
|
|
4137
|
+
wrapper.classList.add("fb-field-wrapper-disabled");
|
|
4138
|
+
wrapper.setAttribute("data-conditionally-disabled", "true");
|
|
4139
|
+
}
|
|
4140
|
+
} catch (error) {
|
|
4141
|
+
console.error(`Error re-evaluating enableIf for field "${element.key}":`, error);
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
function setupEnableIfListeners(wrapper, element, ctx) {
|
|
4145
|
+
if (!element.enableIf) {
|
|
4146
|
+
return;
|
|
4147
|
+
}
|
|
4148
|
+
const formRoot = ctx.state.formRoot;
|
|
4149
|
+
if (!formRoot) {
|
|
4150
|
+
console.error(`Cannot setup enableIf listeners: formRoot is null`);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
const condition = element.enableIf;
|
|
4154
|
+
const scope = condition.scope ?? "relative";
|
|
4155
|
+
const dependencyKey = condition.key;
|
|
4156
|
+
let dependencyFieldPath;
|
|
4157
|
+
if (scope === "relative" && ctx.path) {
|
|
4158
|
+
dependencyFieldPath = `${ctx.path}.${dependencyKey}`;
|
|
4159
|
+
} else {
|
|
4160
|
+
dependencyFieldPath = dependencyKey;
|
|
4161
|
+
}
|
|
4162
|
+
const dependencyInput = formRoot.querySelector(
|
|
4163
|
+
`[name="${dependencyFieldPath}"]`
|
|
4164
|
+
);
|
|
4165
|
+
if (!dependencyInput) {
|
|
4166
|
+
const observer = new MutationObserver(() => {
|
|
4167
|
+
const input = formRoot.querySelector(
|
|
4168
|
+
`[name="${dependencyFieldPath}"]`
|
|
4169
|
+
);
|
|
4170
|
+
if (input) {
|
|
4171
|
+
input.addEventListener("change", () => {
|
|
4172
|
+
reevaluateEnableIf(wrapper, element, ctx);
|
|
4173
|
+
});
|
|
4174
|
+
input.addEventListener("input", () => {
|
|
4175
|
+
reevaluateEnableIf(wrapper, element, ctx);
|
|
4176
|
+
});
|
|
4177
|
+
observer.disconnect();
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
observer.observe(formRoot, {
|
|
4181
|
+
childList: true,
|
|
4182
|
+
subtree: true
|
|
4183
|
+
});
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
dependencyInput.addEventListener("change", () => {
|
|
4187
|
+
reevaluateEnableIf(wrapper, element, ctx);
|
|
4188
|
+
});
|
|
4189
|
+
dependencyInput.addEventListener("input", () => {
|
|
4190
|
+
reevaluateEnableIf(wrapper, element, ctx);
|
|
4191
|
+
});
|
|
3878
4192
|
}
|
|
3879
4193
|
function createFieldLabel(element) {
|
|
3880
4194
|
const title = document.createElement("label");
|
|
@@ -3992,10 +4306,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
3992
4306
|
}
|
|
3993
4307
|
}
|
|
3994
4308
|
function renderElement2(element, ctx) {
|
|
3995
|
-
const
|
|
3996
|
-
if (hiddenElement) {
|
|
3997
|
-
return hiddenElement;
|
|
3998
|
-
}
|
|
4309
|
+
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
3999
4310
|
const wrapper = document.createElement("div");
|
|
4000
4311
|
wrapper.className = "mb-6 fb-field-wrapper";
|
|
4001
4312
|
wrapper.setAttribute("data-field-key", element.key);
|
|
@@ -4003,6 +4314,12 @@ function renderElement2(element, ctx) {
|
|
|
4003
4314
|
wrapper.appendChild(label);
|
|
4004
4315
|
const pathKey = pathJoin(ctx.path, element.key);
|
|
4005
4316
|
dispatchToRenderer(element, ctx, wrapper, pathKey);
|
|
4317
|
+
if (initiallyDisabled) {
|
|
4318
|
+
wrapper.style.display = "none";
|
|
4319
|
+
wrapper.classList.add("fb-field-wrapper-disabled");
|
|
4320
|
+
wrapper.setAttribute("data-conditionally-disabled", "true");
|
|
4321
|
+
}
|
|
4322
|
+
setupEnableIfListeners(wrapper, element, ctx);
|
|
4006
4323
|
return wrapper;
|
|
4007
4324
|
}
|
|
4008
4325
|
setRenderElement(renderElement2);
|
|
@@ -4661,15 +4978,16 @@ var FormBuilderInstance = class {
|
|
|
4661
4978
|
event.preventDefault();
|
|
4662
4979
|
event.stopPropagation();
|
|
4663
4980
|
const hintValuesJson = target.getAttribute("data-hint-values");
|
|
4981
|
+
const isRootHint = target.getAttribute("data-root-hint") === "true";
|
|
4664
4982
|
const containerKey = target.getAttribute("data-container-key");
|
|
4665
|
-
if (!hintValuesJson || !containerKey) {
|
|
4983
|
+
if (!hintValuesJson || !isRootHint && !containerKey) {
|
|
4666
4984
|
console.warn("Prefill hint missing required data attributes");
|
|
4667
4985
|
return;
|
|
4668
4986
|
}
|
|
4669
4987
|
try {
|
|
4670
4988
|
const hintValues = JSON.parse(hintValuesJson);
|
|
4671
4989
|
for (const fieldKey in hintValues) {
|
|
4672
|
-
const fullPath = `${containerKey}.${fieldKey}`;
|
|
4990
|
+
const fullPath = isRootHint ? fieldKey : `${containerKey}.${fieldKey}`;
|
|
4673
4991
|
const value = hintValues[fieldKey];
|
|
4674
4992
|
this.updateField(fullPath, value);
|
|
4675
4993
|
}
|
|
@@ -4677,6 +4995,27 @@ var FormBuilderInstance = class {
|
|
|
4677
4995
|
console.error("Error parsing prefill hint values:", error);
|
|
4678
4996
|
}
|
|
4679
4997
|
}
|
|
4998
|
+
/**
|
|
4999
|
+
* Create root-level prefill hints UI
|
|
5000
|
+
*/
|
|
5001
|
+
createRootPrefillHints(hints) {
|
|
5002
|
+
const hintsContainer = document.createElement("div");
|
|
5003
|
+
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
|
|
5004
|
+
hints.forEach((hint) => {
|
|
5005
|
+
const hintButton = document.createElement("button");
|
|
5006
|
+
hintButton.type = "button";
|
|
5007
|
+
hintButton.className = "fb-prefill-hint";
|
|
5008
|
+
if (hint.icon) {
|
|
5009
|
+
hintButton.textContent = `${hint.icon} ${hint.label}`;
|
|
5010
|
+
} else {
|
|
5011
|
+
hintButton.textContent = hint.label;
|
|
5012
|
+
}
|
|
5013
|
+
hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
|
|
5014
|
+
hintButton.setAttribute("data-root-hint", "true");
|
|
5015
|
+
hintsContainer.appendChild(hintButton);
|
|
5016
|
+
});
|
|
5017
|
+
return hintsContainer;
|
|
5018
|
+
}
|
|
4680
5019
|
/**
|
|
4681
5020
|
* Render form from schema
|
|
4682
5021
|
*/
|
|
@@ -4692,8 +5031,19 @@ var FormBuilderInstance = class {
|
|
|
4692
5031
|
clear(root);
|
|
4693
5032
|
root.setAttribute("data-fb-root", "true");
|
|
4694
5033
|
injectThemeVariables(root, this.state.config.theme);
|
|
4695
|
-
const
|
|
4696
|
-
|
|
5034
|
+
const rootContainer = document.createElement("div");
|
|
5035
|
+
rootContainer.className = "space-y-6";
|
|
5036
|
+
if (schema.prefillHints && !this.state.config.readonly) {
|
|
5037
|
+
const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
|
|
5038
|
+
rootContainer.appendChild(hintsContainer);
|
|
5039
|
+
}
|
|
5040
|
+
const fieldsWrapper = document.createElement("div");
|
|
5041
|
+
const columns = schema.columns || 1;
|
|
5042
|
+
if (columns === 1) {
|
|
5043
|
+
fieldsWrapper.className = "space-y-4";
|
|
5044
|
+
} else {
|
|
5045
|
+
fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5046
|
+
}
|
|
4697
5047
|
schema.elements.forEach((element) => {
|
|
4698
5048
|
if (element.hidden) {
|
|
4699
5049
|
return;
|
|
@@ -4702,13 +5052,14 @@ var FormBuilderInstance = class {
|
|
|
4702
5052
|
path: "",
|
|
4703
5053
|
prefill: prefill || {},
|
|
4704
5054
|
formData: prefill || {},
|
|
4705
|
-
// Pass complete root data for
|
|
5055
|
+
// Pass complete root data for enableIf evaluation
|
|
4706
5056
|
state: this.state,
|
|
4707
5057
|
instance: this
|
|
4708
5058
|
});
|
|
4709
|
-
|
|
5059
|
+
fieldsWrapper.appendChild(block);
|
|
4710
5060
|
});
|
|
4711
|
-
|
|
5061
|
+
rootContainer.appendChild(fieldsWrapper);
|
|
5062
|
+
root.appendChild(rootContainer);
|
|
4712
5063
|
if (!this.state.config.readonly) {
|
|
4713
5064
|
root.addEventListener("click", this.handlePrefillHintClick.bind(this));
|
|
4714
5065
|
}
|
|
@@ -4750,15 +5101,18 @@ var FormBuilderInstance = class {
|
|
|
4750
5101
|
};
|
|
4751
5102
|
setValidateElement(validateElement2);
|
|
4752
5103
|
this.state.schema.elements.forEach((element) => {
|
|
4753
|
-
if (element.
|
|
5104
|
+
if (element.enableIf) {
|
|
4754
5105
|
try {
|
|
4755
|
-
const
|
|
4756
|
-
|
|
5106
|
+
const shouldEnable = evaluateEnableCondition(
|
|
5107
|
+
element.enableIf,
|
|
5108
|
+
data
|
|
5109
|
+
);
|
|
5110
|
+
if (!shouldEnable) {
|
|
4757
5111
|
return;
|
|
4758
5112
|
}
|
|
4759
5113
|
} catch (error) {
|
|
4760
5114
|
console.error(
|
|
4761
|
-
`Error evaluating
|
|
5115
|
+
`Error evaluating enableIf for field "${element.key}" during validation:`,
|
|
4762
5116
|
error
|
|
4763
5117
|
);
|
|
4764
5118
|
}
|
|
@@ -4846,7 +5200,9 @@ var FormBuilderInstance = class {
|
|
|
4846
5200
|
}
|
|
4847
5201
|
if (element.type === "container" || element.type === "group") {
|
|
4848
5202
|
const containerElement = element;
|
|
4849
|
-
const nestedData = this.buildHiddenFieldsData(
|
|
5203
|
+
const nestedData = this.buildHiddenFieldsData(
|
|
5204
|
+
containerElement.elements
|
|
5205
|
+
);
|
|
4850
5206
|
if (Object.keys(nestedData).length > 0) {
|
|
4851
5207
|
if (!(key in data)) {
|
|
4852
5208
|
data[key] = nestedData;
|
|
@@ -4864,7 +5220,9 @@ var FormBuilderInstance = class {
|
|
|
4864
5220
|
*/
|
|
4865
5221
|
setFormData(data) {
|
|
4866
5222
|
if (!this.state.schema || !this.state.formRoot) {
|
|
4867
|
-
console.warn(
|
|
5223
|
+
console.warn(
|
|
5224
|
+
"setFormData: Form not initialized. Call renderForm() first."
|
|
5225
|
+
);
|
|
4868
5226
|
return;
|
|
4869
5227
|
}
|
|
4870
5228
|
for (const fieldPath in data) {
|
|
@@ -4878,20 +5236,27 @@ var FormBuilderInstance = class {
|
|
|
4878
5236
|
*/
|
|
4879
5237
|
updateField(fieldPath, value) {
|
|
4880
5238
|
if (!this.state.schema || !this.state.formRoot) {
|
|
4881
|
-
console.warn(
|
|
5239
|
+
console.warn(
|
|
5240
|
+
"updateField: Form not initialized. Call renderForm() first."
|
|
5241
|
+
);
|
|
4882
5242
|
return;
|
|
4883
5243
|
}
|
|
4884
5244
|
const schemaElement = this.findSchemaElement(fieldPath);
|
|
4885
5245
|
if (!schemaElement) {
|
|
4886
|
-
console.warn(
|
|
5246
|
+
console.warn(
|
|
5247
|
+
`updateField: Schema element not found for path "${fieldPath}"`
|
|
5248
|
+
);
|
|
4887
5249
|
return;
|
|
4888
5250
|
}
|
|
4889
5251
|
const domElement = this.findFormElementByFieldPath(fieldPath);
|
|
4890
5252
|
if (!domElement) {
|
|
4891
|
-
console.warn(
|
|
5253
|
+
console.warn(
|
|
5254
|
+
`updateField: DOM element not found for path "${fieldPath}"`
|
|
5255
|
+
);
|
|
4892
5256
|
return;
|
|
4893
5257
|
}
|
|
4894
5258
|
this.updateFieldValue(domElement, schemaElement, fieldPath, value);
|
|
5259
|
+
this.reevaluateConditionalFields();
|
|
4895
5260
|
if (this.state.config.onChange || this.state.config.onFieldChange) {
|
|
4896
5261
|
this.triggerOnChange(fieldPath, value);
|
|
4897
5262
|
}
|
|
@@ -4920,7 +5285,7 @@ var FormBuilderInstance = class {
|
|
|
4920
5285
|
}
|
|
4921
5286
|
}
|
|
4922
5287
|
/**
|
|
4923
|
-
* Re-evaluate all conditional fields (
|
|
5288
|
+
* Re-evaluate all conditional fields (enableIf) based on current form data
|
|
4924
5289
|
* This is called automatically when form data changes (via onChange events)
|
|
4925
5290
|
*/
|
|
4926
5291
|
reevaluateConditionalFields() {
|
|
@@ -4929,45 +5294,80 @@ var FormBuilderInstance = class {
|
|
|
4929
5294
|
const checkElements = (elements, currentPath) => {
|
|
4930
5295
|
elements.forEach((element) => {
|
|
4931
5296
|
const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
|
|
4932
|
-
if (element.
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
5297
|
+
if (element.enableIf) {
|
|
5298
|
+
let fieldWrapper = null;
|
|
5299
|
+
if (currentPath) {
|
|
5300
|
+
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
5301
|
+
if (pathMatch) {
|
|
5302
|
+
const containerKey = pathMatch[1];
|
|
5303
|
+
const containerIndex = pathMatch[2];
|
|
5304
|
+
const containerElement = this.state.formRoot.querySelector(
|
|
5305
|
+
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
5306
|
+
);
|
|
5307
|
+
if (containerElement) {
|
|
5308
|
+
fieldWrapper = containerElement.querySelector(
|
|
5309
|
+
`[data-field-key="${element.key}"]`
|
|
5310
|
+
);
|
|
5311
|
+
}
|
|
5312
|
+
} else {
|
|
5313
|
+
const containerElement = this.state.formRoot.querySelector(
|
|
5314
|
+
`[data-container="${currentPath}"]`
|
|
5315
|
+
);
|
|
5316
|
+
if (containerElement) {
|
|
5317
|
+
fieldWrapper = containerElement.querySelector(
|
|
5318
|
+
`[data-field-key="${element.key}"]`
|
|
5319
|
+
);
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
} else {
|
|
5323
|
+
fieldWrapper = this.state.formRoot.querySelector(
|
|
5324
|
+
`[data-field-key="${element.key}"]`
|
|
5325
|
+
);
|
|
5326
|
+
}
|
|
5327
|
+
if (fieldWrapper) {
|
|
5328
|
+
const wrapper = fieldWrapper;
|
|
4937
5329
|
try {
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
5330
|
+
let containerData = void 0;
|
|
5331
|
+
const scope = element.enableIf.scope ?? "relative";
|
|
5332
|
+
if (scope === "relative" && currentPath) {
|
|
5333
|
+
containerData = getValueByPath(formData, currentPath);
|
|
5334
|
+
}
|
|
5335
|
+
const shouldEnable = evaluateEnableCondition(
|
|
5336
|
+
element.enableIf,
|
|
5337
|
+
formData,
|
|
5338
|
+
// Use complete formData for absolute scope
|
|
5339
|
+
containerData
|
|
5340
|
+
// Use container-specific data for relative scope
|
|
4942
5341
|
);
|
|
4943
|
-
const
|
|
4944
|
-
if (
|
|
5342
|
+
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
5343
|
+
if (shouldEnable && isCurrentlyDisabled) {
|
|
5344
|
+
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
5345
|
+
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
4945
5346
|
const newWrapper = renderElement2(element, {
|
|
4946
|
-
path:
|
|
4947
|
-
// Use
|
|
4948
|
-
prefill:
|
|
4949
|
-
// Use complete formData for root-level elements
|
|
5347
|
+
path: currentPath,
|
|
5348
|
+
// Use container path (empty string for root-level)
|
|
5349
|
+
prefill: prefillContext,
|
|
4950
5350
|
formData,
|
|
4951
|
-
// Pass complete formData for
|
|
5351
|
+
// Pass complete formData for enableIf evaluation
|
|
4952
5352
|
state: this.state,
|
|
4953
5353
|
instance: this
|
|
4954
5354
|
});
|
|
4955
5355
|
wrapper.parentNode?.replaceChild(newWrapper, wrapper);
|
|
4956
|
-
} else if (!
|
|
4957
|
-
const
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
wrapper.parentNode?.replaceChild(
|
|
5356
|
+
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
5357
|
+
const disabledWrapper = document.createElement("div");
|
|
5358
|
+
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
5359
|
+
disabledWrapper.style.display = "none";
|
|
5360
|
+
disabledWrapper.setAttribute("data-field-key", element.key);
|
|
5361
|
+
disabledWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
5362
|
+
wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
|
|
4963
5363
|
}
|
|
4964
5364
|
} catch (error) {
|
|
4965
5365
|
console.error(
|
|
4966
|
-
`Error re-evaluating
|
|
5366
|
+
`Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
|
|
4967
5367
|
error
|
|
4968
5368
|
);
|
|
4969
5369
|
}
|
|
4970
|
-
}
|
|
5370
|
+
}
|
|
4971
5371
|
}
|
|
4972
5372
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
4973
5373
|
const containerData = formData?.[element.key];
|