@dmitryvim/form-builder 0.2.8 → 0.2.10

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.
@@ -66,6 +66,41 @@ function validateSchema(schema) {
66
66
  errors.push("Schema missing elements array");
67
67
  return errors;
68
68
  }
69
+ if ("columns" in schema && schema.columns !== void 0) {
70
+ const columns = schema.columns;
71
+ const validColumns = [1, 2, 3, 4];
72
+ if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
73
+ errors.push(`schema.columns must be 1, 2, 3, or 4 (got ${columns})`);
74
+ }
75
+ }
76
+ if ("prefillHints" in schema && schema.prefillHints) {
77
+ const prefillHints = schema.prefillHints;
78
+ if (Array.isArray(prefillHints)) {
79
+ prefillHints.forEach((hint, hintIndex) => {
80
+ if (!hint.label || typeof hint.label !== "string") {
81
+ errors.push(
82
+ `schema.prefillHints[${hintIndex}] must have a 'label' property of type string`
83
+ );
84
+ }
85
+ if (!hint.values || typeof hint.values !== "object") {
86
+ errors.push(
87
+ `schema.prefillHints[${hintIndex}] must have a 'values' property of type object`
88
+ );
89
+ } else {
90
+ for (const fieldKey in hint.values) {
91
+ const fieldExists = schema.elements.some(
92
+ (element) => element.key === fieldKey
93
+ );
94
+ if (!fieldExists) {
95
+ errors.push(
96
+ `schema.prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
97
+ );
98
+ }
99
+ }
100
+ }
101
+ });
102
+ }
103
+ }
69
104
  function validateElements(elements, path) {
70
105
  elements.forEach((element, index) => {
71
106
  const elementPath = `${path}[${index}]`;
@@ -75,17 +110,17 @@ function validateSchema(schema) {
75
110
  if (!element.key) {
76
111
  errors.push(`${elementPath}: missing key`);
77
112
  }
78
- if (element.displayIf) {
79
- const displayIf = element.displayIf;
80
- if (!displayIf.key || typeof displayIf.key !== "string") {
113
+ if (element.enableIf) {
114
+ const enableIf = element.enableIf;
115
+ if (!enableIf.key || typeof enableIf.key !== "string") {
81
116
  errors.push(
82
- `${elementPath}: displayIf must have a 'key' property of type string`
117
+ `${elementPath}: enableIf must have a 'key' property of type string`
83
118
  );
84
119
  }
85
- const hasOperator = "equals" in displayIf;
120
+ const hasOperator = "equals" in enableIf;
86
121
  if (!hasOperator) {
87
122
  errors.push(
88
- `${elementPath}: displayIf must have at least one operator (equals, etc.)`
123
+ `${elementPath}: enableIf must have at least one operator (equals, etc.)`
89
124
  );
90
125
  }
91
126
  }
@@ -163,7 +198,7 @@ function clear(node) {
163
198
  while (node.firstChild) node.removeChild(node.firstChild);
164
199
  }
165
200
 
166
- // src/utils/display-conditions.ts
201
+ // src/utils/enable-conditions.ts
167
202
  function getValueByPath(data, path) {
168
203
  if (!data || typeof data !== "object") {
169
204
  return void 0;
@@ -189,18 +224,28 @@ function getValueByPath(data, path) {
189
224
  }
190
225
  return current;
191
226
  }
192
- function evaluateDisplayCondition(condition, formData) {
227
+ function evaluateEnableCondition(condition, formData, containerData) {
228
+ var _a;
193
229
  if (!condition || !condition.key) {
230
+ throw new Error("Invalid enableIf condition: must have a 'key' property");
231
+ }
232
+ const scope = (_a = condition.scope) != null ? _a : "relative";
233
+ let dataSource;
234
+ if (scope === "relative") {
235
+ dataSource = containerData != null ? containerData : formData;
236
+ } else if (scope === "absolute") {
237
+ dataSource = formData;
238
+ } else {
194
239
  throw new Error(
195
- "Invalid displayIf condition: must have a 'key' property"
240
+ `Invalid enableIf scope: must be "relative" or "absolute" (got "${scope}")`
196
241
  );
197
242
  }
198
- const actualValue = getValueByPath(formData, condition.key);
243
+ const actualValue = getValueByPath(dataSource, condition.key);
199
244
  if ("equals" in condition) {
200
245
  return deepEqual(actualValue, condition.equals);
201
246
  }
202
247
  throw new Error(
203
- `Invalid displayIf condition: no recognized operator (equals, etc.)`
248
+ `Invalid enableIf condition: no recognized operator (equals, etc.)`
204
249
  );
205
250
  }
206
251
  function deepEqual(a, b) {
@@ -213,7 +258,7 @@ function deepEqual(a, b) {
213
258
  } catch (e) {
214
259
  if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
215
260
  console.warn(
216
- "deepEqual: Circular reference detected in displayIf comparison, using reference equality"
261
+ "deepEqual: Circular reference detected in enableIf comparison, using reference equality"
217
262
  );
218
263
  return a === b;
219
264
  }
@@ -266,7 +311,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
266
311
  }
267
312
  if (!state.config.readonly && ctx.instance) {
268
313
  const handleChange = () => {
269
- ctx.instance.triggerOnChange(pathKey, textInput.value);
314
+ const value = textInput.value === "" ? null : textInput.value;
315
+ ctx.instance.triggerOnChange(pathKey, value);
270
316
  };
271
317
  textInput.addEventListener("blur", handleChange);
272
318
  textInput.addEventListener("input", handleChange);
@@ -345,7 +391,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
345
391
  }
346
392
  if (!state.config.readonly && ctx.instance) {
347
393
  const handleChange = () => {
348
- ctx.instance.triggerOnChange(textInput.name, textInput.value);
394
+ const value2 = textInput.value === "" ? null : textInput.value;
395
+ ctx.instance.triggerOnChange(textInput.name, value2);
349
396
  };
350
397
  textInput.addEventListener("blur", handleChange);
351
398
  textInput.addEventListener("input", handleChange);
@@ -417,7 +464,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
417
464
  font-size: var(--fb-font-size);
418
465
  transition: all var(--fb-transition-duration);
419
466
  `;
420
- addBtn.textContent = `+ Add ${element.label || "Text"}`;
467
+ addBtn.textContent = "+";
421
468
  addBtn.addEventListener("mouseenter", () => {
422
469
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
423
470
  });
@@ -513,20 +560,20 @@ function validateTextElement(element, key, context) {
513
560
  }
514
561
  };
515
562
  if (element.multiple) {
516
- const inputs = scopeRoot.querySelectorAll(
517
- `[name^="${key}["]`
518
- );
563
+ const inputs = scopeRoot.querySelectorAll(`[name^="${key}["]`);
519
564
  const values = [];
565
+ const rawValues = [];
520
566
  inputs.forEach((input, index) => {
521
567
  var _a2;
522
568
  const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
523
- values.push(val);
569
+ rawValues.push(val);
570
+ values.push(val === "" ? null : val);
524
571
  validateTextInput(input, val, `${key}[${index}]`);
525
572
  });
526
573
  if (!skipValidation) {
527
574
  const minCount = (_a = element.minCount) != null ? _a : 1;
528
575
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
529
- const filteredValues = values.filter((v) => v.trim() !== "");
576
+ const filteredValues = rawValues.filter((v) => v.trim() !== "");
530
577
  if (element.required && filteredValues.length === 0) {
531
578
  errors.push(`${key}: required`);
532
579
  }
@@ -539,17 +586,17 @@ function validateTextElement(element, key, context) {
539
586
  }
540
587
  return { value: values, errors };
541
588
  } else {
542
- const input = scopeRoot.querySelector(
543
- `[name$="${key}"]`
544
- );
589
+ const input = scopeRoot.querySelector(`[name$="${key}"]`);
545
590
  const val = (_c = input == null ? void 0 : input.value) != null ? _c : "";
546
591
  if (!skipValidation && element.required && val === "") {
547
592
  errors.push(`${key}: required`);
548
593
  markValidity(input, "required");
549
- return { value: "", errors };
594
+ return { value: null, errors };
550
595
  }
551
- validateTextInput(input, val, key);
552
- return { value: val, errors };
596
+ if (input) {
597
+ validateTextInput(input, val, key);
598
+ }
599
+ return { value: val === "" ? null : val, errors };
553
600
  }
554
601
  }
555
602
  function updateTextField(element, fieldPath, value, context) {
@@ -561,9 +608,7 @@ function updateTextField(element, fieldPath, value, context) {
561
608
  );
562
609
  return;
563
610
  }
564
- const inputs = scopeRoot.querySelectorAll(
565
- `[name^="${fieldPath}["]`
566
- );
611
+ const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["]`);
567
612
  inputs.forEach((input, index) => {
568
613
  if (index < value.length) {
569
614
  input.value = value[index] != null ? String(value[index]) : "";
@@ -577,9 +622,7 @@ function updateTextField(element, fieldPath, value, context) {
577
622
  );
578
623
  }
579
624
  } else {
580
- const input = scopeRoot.querySelector(
581
- `[name="${fieldPath}"]`
582
- );
625
+ const input = scopeRoot.querySelector(`[name="${fieldPath}"]`);
583
626
  if (input) {
584
627
  input.value = value != null ? String(value) : "";
585
628
  input.classList.remove("invalid");
@@ -600,7 +643,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
600
643
  textareaInput.readOnly = state.config.readonly;
601
644
  if (!state.config.readonly && ctx.instance) {
602
645
  const handleChange = () => {
603
- ctx.instance.triggerOnChange(pathKey, textareaInput.value);
646
+ const value = textareaInput.value === "" ? null : textareaInput.value;
647
+ ctx.instance.triggerOnChange(pathKey, value);
604
648
  };
605
649
  textareaInput.addEventListener("blur", handleChange);
606
650
  textareaInput.addEventListener("input", handleChange);
@@ -644,7 +688,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
644
688
  textareaInput.readOnly = state.config.readonly;
645
689
  if (!state.config.readonly && ctx.instance) {
646
690
  const handleChange = () => {
647
- ctx.instance.triggerOnChange(textareaInput.name, textareaInput.value);
691
+ const value2 = textareaInput.value === "" ? null : textareaInput.value;
692
+ ctx.instance.triggerOnChange(textareaInput.name, value2);
648
693
  };
649
694
  textareaInput.addEventListener("blur", handleChange);
650
695
  textareaInput.addEventListener("input", handleChange);
@@ -670,7 +715,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
670
715
  removeBtn = document.createElement("button");
671
716
  removeBtn.type = "button";
672
717
  removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
673
- removeBtn.innerHTML = "\u2715 Remove";
718
+ removeBtn.innerHTML = "\u2715";
674
719
  removeBtn.onclick = () => {
675
720
  const currentIndex = Array.from(container.children).indexOf(
676
721
  item
@@ -698,7 +743,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
698
743
  const addBtn = document.createElement("button");
699
744
  addBtn.type = "button";
700
745
  addBtn.className = "add-textarea-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
701
- addBtn.textContent = `+ Add ${element.label || "Textarea"}`;
746
+ addBtn.textContent = "+";
702
747
  addBtn.onclick = () => {
703
748
  values.push(element.default || "");
704
749
  addTextareaItem(element.default || "");
@@ -841,7 +886,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
841
886
  const addBtn = document.createElement("button");
842
887
  addBtn.type = "button";
843
888
  addBtn.className = "add-number-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
844
- addBtn.textContent = `+ Add ${element.label || "Number"}`;
889
+ addBtn.textContent = "+";
845
890
  addBtn.onclick = () => {
846
891
  values.push(element.default || "");
847
892
  addNumberItem(element.default || "");
@@ -1127,7 +1172,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1127
1172
  const addBtn = document.createElement("button");
1128
1173
  addBtn.type = "button";
1129
1174
  addBtn.className = "add-select-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
1130
- addBtn.textContent = `+ Add ${element.label || "Selection"}`;
1175
+ addBtn.textContent = "+";
1131
1176
  addBtn.onclick = () => {
1132
1177
  var _a2, _b2;
1133
1178
  const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
@@ -1319,7 +1364,9 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
1319
1364
  return newContainer;
1320
1365
  }
1321
1366
  function attachVideoButtonHandlers(container, resourceId, state, deps) {
1322
- const changeBtn = container.querySelector(".change-file-btn");
1367
+ const changeBtn = container.querySelector(
1368
+ ".change-file-btn"
1369
+ );
1323
1370
  if (changeBtn) {
1324
1371
  changeBtn.onclick = (e) => {
1325
1372
  e.stopPropagation();
@@ -1328,7 +1375,9 @@ function attachVideoButtonHandlers(container, resourceId, state, deps) {
1328
1375
  }
1329
1376
  };
1330
1377
  }
1331
- const deleteBtn = container.querySelector(".delete-file-btn");
1378
+ const deleteBtn = container.querySelector(
1379
+ ".delete-file-btn"
1380
+ );
1332
1381
  if (deleteBtn) {
1333
1382
  deleteBtn.onclick = (e) => {
1334
1383
  e.stopPropagation();
@@ -1370,7 +1419,9 @@ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
1370
1419
  source.src = thumbnailUrl;
1371
1420
  source.type = videoType;
1372
1421
  video.appendChild(source);
1373
- video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1422
+ video.appendChild(
1423
+ document.createTextNode("Your browser does not support the video tag.")
1424
+ );
1374
1425
  container.appendChild(video);
1375
1426
  }
1376
1427
  function renderDeleteButton(container, resourceId, state) {
@@ -1472,7 +1523,13 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
1472
1523
  deps
1473
1524
  );
1474
1525
  } else {
1475
- await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
1526
+ await renderUploadedFilePreview(
1527
+ container,
1528
+ resourceId,
1529
+ fileName,
1530
+ meta,
1531
+ state
1532
+ );
1476
1533
  }
1477
1534
  }
1478
1535
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -1631,7 +1688,9 @@ function renderResourcePills(container, rids, state, onRemove) {
1631
1688
  if (fileInput) fileInput.click();
1632
1689
  };
1633
1690
  textContainer.appendChild(uploadLink);
1634
- textContainer.appendChild(document.createTextNode(` ${t("dragDropText", state)}`));
1691
+ textContainer.appendChild(
1692
+ document.createTextNode(` ${t("dragDropText", state)}`)
1693
+ );
1635
1694
  container.appendChild(gridContainer);
1636
1695
  container.appendChild(textContainer);
1637
1696
  return;
@@ -2101,7 +2160,14 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
2101
2160
  const dragHandler = (files) => {
2102
2161
  if (files.length > 0) {
2103
2162
  const deps = { picker, fileUploadHandler, dragHandler };
2104
- handleFileSelect(files[0], fileContainer, pathKey, state, deps, ctx.instance);
2163
+ handleFileSelect(
2164
+ files[0],
2165
+ fileContainer,
2166
+ pathKey,
2167
+ state,
2168
+ deps,
2169
+ ctx.instance
2170
+ );
2105
2171
  }
2106
2172
  };
2107
2173
  if (initial) {
@@ -2125,7 +2191,14 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
2125
2191
  picker.onchange = () => {
2126
2192
  if (picker.files && picker.files.length > 0) {
2127
2193
  const deps = { picker, fileUploadHandler, dragHandler };
2128
- handleFileSelect(picker.files[0], fileContainer, pathKey, state, deps, ctx.instance);
2194
+ handleFileSelect(
2195
+ picker.files[0],
2196
+ fileContainer,
2197
+ pathKey,
2198
+ state,
2199
+ deps,
2200
+ ctx.instance
2201
+ );
2129
2202
  }
2130
2203
  };
2131
2204
  fileWrapper.appendChild(fileContainer);
@@ -2191,8 +2264,22 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
2191
2264
  const initialFiles = ctx.prefill[element.key] || [];
2192
2265
  addPrefillFilesToIndex(initialFiles, state);
2193
2266
  updateFilesList2();
2194
- setupFilesDropHandler(filesContainer, initialFiles, state, updateFilesList2, pathKey, ctx.instance);
2195
- setupFilesPickerHandler(filesPicker, initialFiles, state, updateFilesList2, pathKey, ctx.instance);
2267
+ setupFilesDropHandler(
2268
+ filesContainer,
2269
+ initialFiles,
2270
+ state,
2271
+ updateFilesList2,
2272
+ pathKey,
2273
+ ctx.instance
2274
+ );
2275
+ setupFilesPickerHandler(
2276
+ filesPicker,
2277
+ initialFiles,
2278
+ state,
2279
+ updateFilesList2,
2280
+ pathKey,
2281
+ ctx.instance
2282
+ );
2196
2283
  filesContainer.appendChild(list);
2197
2284
  filesWrapper.appendChild(filesContainer);
2198
2285
  filesWrapper.appendChild(filesPicker);
@@ -2255,8 +2342,22 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
2255
2342
  if (existingCount) existingCount.remove();
2256
2343
  filesWrapper.appendChild(countInfo);
2257
2344
  };
2258
- setupFilesDropHandler(filesContainer, initialFiles, state, updateFilesDisplay, pathKey, ctx.instance);
2259
- setupFilesPickerHandler(filesPicker, initialFiles, state, updateFilesDisplay, pathKey, ctx.instance);
2345
+ setupFilesDropHandler(
2346
+ filesContainer,
2347
+ initialFiles,
2348
+ state,
2349
+ updateFilesDisplay,
2350
+ pathKey,
2351
+ ctx.instance
2352
+ );
2353
+ setupFilesPickerHandler(
2354
+ filesPicker,
2355
+ initialFiles,
2356
+ state,
2357
+ updateFilesDisplay,
2358
+ pathKey,
2359
+ ctx.instance
2360
+ );
2260
2361
  updateFilesDisplay();
2261
2362
  wrapper.appendChild(filesWrapper);
2262
2363
  }
@@ -2660,7 +2761,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2660
2761
  font-size: var(--fb-font-size);
2661
2762
  transition: all var(--fb-transition-duration);
2662
2763
  `;
2663
- addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2764
+ addBtn.textContent = "+";
2664
2765
  addBtn.addEventListener("mouseenter", () => {
2665
2766
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2666
2767
  });
@@ -2746,9 +2847,7 @@ function validateColourElement(element, key, context) {
2746
2847
  return normalized;
2747
2848
  };
2748
2849
  if (element.multiple) {
2749
- const hexInputs = scopeRoot.querySelectorAll(
2750
- `.colour-hex-input`
2751
- );
2850
+ const hexInputs = scopeRoot.querySelectorAll(`[name^="${key}["].colour-hex-input`);
2752
2851
  const values = [];
2753
2852
  hexInputs.forEach((input, index) => {
2754
2853
  var _a2;
@@ -2794,9 +2893,7 @@ function updateColourField(element, fieldPath, value, context) {
2794
2893
  );
2795
2894
  return;
2796
2895
  }
2797
- const hexInputs = scopeRoot.querySelectorAll(
2798
- `.colour-hex-input`
2799
- );
2896
+ const hexInputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["].colour-hex-input`);
2800
2897
  hexInputs.forEach((hexInput, index) => {
2801
2898
  if (index < value.length) {
2802
2899
  const normalized = normalizeColourValue(value[index]);
@@ -2806,7 +2903,9 @@ function updateColourField(element, fieldPath, value, context) {
2806
2903
  const wrapper = hexInput.closest(".colour-picker-wrapper");
2807
2904
  if (wrapper) {
2808
2905
  const swatch = wrapper.querySelector(".colour-swatch");
2809
- const colourInput = wrapper.querySelector(".colour-picker-hidden");
2906
+ const colourInput = wrapper.querySelector(
2907
+ ".colour-picker-hidden"
2908
+ );
2810
2909
  if (swatch) {
2811
2910
  swatch.style.backgroundColor = normalized;
2812
2911
  }
@@ -2833,7 +2932,9 @@ function updateColourField(element, fieldPath, value, context) {
2833
2932
  const wrapper = hexInput.closest(".colour-picker-wrapper");
2834
2933
  if (wrapper) {
2835
2934
  const swatch = wrapper.querySelector(".colour-swatch");
2836
- const colourInput = wrapper.querySelector(".colour-picker-hidden");
2935
+ const colourInput = wrapper.querySelector(
2936
+ ".colour-picker-hidden"
2937
+ );
2837
2938
  if (swatch) {
2838
2939
  swatch.style.backgroundColor = normalized;
2839
2940
  }
@@ -2973,14 +3074,10 @@ function createSliderUI(value, pathKey, element, ctx, readonly) {
2973
3074
  function renderSliderElement(element, ctx, wrapper, pathKey) {
2974
3075
  var _a;
2975
3076
  if (element.min === void 0 || element.min === null) {
2976
- throw new Error(
2977
- `Slider field "${element.key}" requires "min" property`
2978
- );
3077
+ throw new Error(`Slider field "${element.key}" requires "min" property`);
2979
3078
  }
2980
3079
  if (element.max === void 0 || element.max === null) {
2981
- throw new Error(
2982
- `Slider field "${element.key}" requires "max" property`
2983
- );
3080
+ throw new Error(`Slider field "${element.key}" requires "max" property`);
2984
3081
  }
2985
3082
  if (element.min >= element.max) {
2986
3083
  throw new Error(
@@ -3010,14 +3107,10 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
3010
3107
  function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
3011
3108
  var _a, _b;
3012
3109
  if (element.min === void 0 || element.min === null) {
3013
- throw new Error(
3014
- `Slider field "${element.key}" requires "min" property`
3015
- );
3110
+ throw new Error(`Slider field "${element.key}" requires "min" property`);
3016
3111
  }
3017
3112
  if (element.max === void 0 || element.max === null) {
3018
- throw new Error(
3019
- `Slider field "${element.key}" requires "max" property`
3020
- );
3113
+ throw new Error(`Slider field "${element.key}" requires "max" property`);
3021
3114
  }
3022
3115
  if (element.min >= element.max) {
3023
3116
  throw new Error(
@@ -3125,7 +3218,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
3125
3218
  font-size: var(--fb-font-size);
3126
3219
  transition: all var(--fb-transition-duration);
3127
3220
  `;
3128
- addBtn.textContent = `+ Add ${element.label || "Slider"}`;
3221
+ addBtn.textContent = "+";
3129
3222
  addBtn.addEventListener("mouseenter", () => {
3130
3223
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3131
3224
  });
@@ -3190,7 +3283,10 @@ function validateSliderElement(element, key, context) {
3190
3283
  `;
3191
3284
  const sliderContainer = input.closest(".slider-container");
3192
3285
  if (sliderContainer && sliderContainer.nextSibling) {
3193
- (_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, sliderContainer.nextSibling);
3286
+ (_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(
3287
+ errorElement,
3288
+ sliderContainer.nextSibling
3289
+ );
3194
3290
  } else if (sliderContainer) {
3195
3291
  (_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
3196
3292
  }
@@ -3362,6 +3458,33 @@ function updateSliderField(element, fieldPath, value, context) {
3362
3458
  }
3363
3459
 
3364
3460
  // src/components/container.ts
3461
+ function extractRootFormData(formRoot) {
3462
+ const data = {};
3463
+ const inputs = formRoot.querySelectorAll(
3464
+ "input, select, textarea"
3465
+ );
3466
+ inputs.forEach((input) => {
3467
+ const fieldName = input.getAttribute("name");
3468
+ if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
3469
+ if (input instanceof HTMLSelectElement) {
3470
+ data[fieldName] = input.value;
3471
+ } else if (input instanceof HTMLInputElement) {
3472
+ if (input.type === "checkbox") {
3473
+ data[fieldName] = input.checked;
3474
+ } else if (input.type === "radio") {
3475
+ if (input.checked) {
3476
+ data[fieldName] = input.value;
3477
+ }
3478
+ } else {
3479
+ data[fieldName] = input.value;
3480
+ }
3481
+ } else if (input instanceof HTMLTextAreaElement) {
3482
+ data[fieldName] = input.value;
3483
+ }
3484
+ }
3485
+ });
3486
+ return data;
3487
+ }
3365
3488
  var renderElementFunc = null;
3366
3489
  function setRenderElement(fn) {
3367
3490
  renderElementFunc = fn;
@@ -3421,7 +3544,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
3421
3544
  prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
3422
3545
  // Sliced data for value population
3423
3546
  formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
3424
- // Complete root data for displayIf evaluation
3547
+ // Complete root data for enableIf evaluation
3425
3548
  state: ctx.state
3426
3549
  };
3427
3550
  element.elements.forEach((child) => {
@@ -3442,15 +3565,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3442
3565
  header.className = "flex justify-between items-center mb-4";
3443
3566
  const left = document.createElement("div");
3444
3567
  left.className = "flex-1";
3445
- const right = document.createElement("div");
3446
- right.className = "flex gap-2";
3447
3568
  const itemsWrap = document.createElement("div");
3448
3569
  itemsWrap.className = "space-y-4";
3449
3570
  containerWrap.appendChild(header);
3450
3571
  header.appendChild(left);
3451
- if (!state.config.readonly) {
3452
- header.appendChild(right);
3453
- }
3454
3572
  if (!ctx.state.config.readonly) {
3455
3573
  const hintsElement = createPrefillHints(element, element.key);
3456
3574
  if (hintsElement) {
@@ -3464,18 +3582,31 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3464
3582
  const createAddButton = () => {
3465
3583
  const add = document.createElement("button");
3466
3584
  add.type = "button";
3467
- add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
3468
- add.textContent = t("addElement", state);
3585
+ add.className = "add-container-btn mt-2 px-3 py-1 rounded";
3586
+ add.style.cssText = `
3587
+ color: var(--fb-primary-color);
3588
+ border: var(--fb-border-width) solid var(--fb-primary-color);
3589
+ background-color: transparent;
3590
+ font-size: var(--fb-font-size);
3591
+ transition: all var(--fb-transition-duration);
3592
+ `;
3593
+ add.textContent = "+";
3594
+ add.addEventListener("mouseenter", () => {
3595
+ add.style.backgroundColor = "var(--fb-background-hover-color)";
3596
+ });
3597
+ add.addEventListener("mouseleave", () => {
3598
+ add.style.backgroundColor = "transparent";
3599
+ });
3469
3600
  add.onclick = () => {
3470
- var _a2;
3471
3601
  if (countItems() < max) {
3472
3602
  const idx = countItems();
3603
+ const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
3473
3604
  const subCtx = {
3474
3605
  state: ctx.state,
3475
3606
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3476
3607
  prefill: {},
3477
- formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
3478
- // Complete root data for displayIf
3608
+ formData: currentFormData
3609
+ // Current root data from DOM for enableIf
3479
3610
  };
3480
3611
  const item = document.createElement("div");
3481
3612
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3496,8 +3627,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3496
3627
  if (!state.config.readonly) {
3497
3628
  const rem = document.createElement("button");
3498
3629
  rem.type = "button";
3499
- rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
3500
- rem.textContent = "\xD7";
3630
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3631
+ rem.style.cssText = `
3632
+ color: var(--fb-error-color);
3633
+ background-color: transparent;
3634
+ transition: background-color var(--fb-transition-duration);
3635
+ `;
3636
+ rem.textContent = "\u2715";
3637
+ rem.addEventListener("mouseenter", () => {
3638
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3639
+ });
3640
+ rem.addEventListener("mouseleave", () => {
3641
+ rem.style.backgroundColor = "transparent";
3642
+ });
3501
3643
  rem.onclick = () => {
3502
3644
  item.remove();
3503
3645
  updateAddButton();
@@ -3513,16 +3655,16 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3513
3655
  };
3514
3656
  const updateAddButton = () => {
3515
3657
  const currentCount = countItems();
3516
- const addBtn = right.querySelector("button");
3517
- if (addBtn) {
3518
- addBtn.disabled = currentCount >= max;
3519
- addBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3658
+ const existingAddBtn = containerWrap.querySelector(
3659
+ ".add-container-btn"
3660
+ );
3661
+ if (existingAddBtn) {
3662
+ existingAddBtn.disabled = currentCount >= max;
3663
+ existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3664
+ existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
3520
3665
  }
3521
3666
  left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? "\u221E" : max})</span>`;
3522
3667
  };
3523
- if (!state.config.readonly) {
3524
- right.appendChild(createAddButton());
3525
- }
3526
3668
  if (pre && Array.isArray(pre)) {
3527
3669
  pre.forEach((prefillObj, idx) => {
3528
3670
  var _a2;
@@ -3531,7 +3673,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3531
3673
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3532
3674
  prefill: prefillObj || {},
3533
3675
  formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
3534
- // Complete root data for displayIf
3676
+ // Complete root data for enableIf
3535
3677
  };
3536
3678
  const item = document.createElement("div");
3537
3679
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3552,8 +3694,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3552
3694
  if (!state.config.readonly) {
3553
3695
  const rem = document.createElement("button");
3554
3696
  rem.type = "button";
3555
- rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
3556
- rem.textContent = "\xD7";
3697
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3698
+ rem.style.cssText = `
3699
+ color: var(--fb-error-color);
3700
+ background-color: transparent;
3701
+ transition: background-color var(--fb-transition-duration);
3702
+ `;
3703
+ rem.textContent = "\u2715";
3704
+ rem.addEventListener("mouseenter", () => {
3705
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3706
+ });
3707
+ rem.addEventListener("mouseleave", () => {
3708
+ rem.style.backgroundColor = "transparent";
3709
+ });
3557
3710
  rem.onclick = () => {
3558
3711
  item.remove();
3559
3712
  updateAddButton();
@@ -3572,7 +3725,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3572
3725
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3573
3726
  prefill: {},
3574
3727
  formData: (_d = ctx.formData) != null ? _d : ctx.prefill
3575
- // Complete root data for displayIf
3728
+ // Complete root data for enableIf
3576
3729
  };
3577
3730
  const item = document.createElement("div");
3578
3731
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3592,8 +3745,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3592
3745
  item.appendChild(childWrapper);
3593
3746
  const rem = document.createElement("button");
3594
3747
  rem.type = "button";
3595
- rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
3596
- rem.textContent = "\xD7";
3748
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3749
+ rem.style.cssText = `
3750
+ color: var(--fb-error-color);
3751
+ background-color: transparent;
3752
+ transition: background-color var(--fb-transition-duration);
3753
+ `;
3754
+ rem.textContent = "\u2715";
3755
+ rem.addEventListener("mouseenter", () => {
3756
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3757
+ });
3758
+ rem.addEventListener("mouseleave", () => {
3759
+ rem.style.backgroundColor = "transparent";
3760
+ });
3597
3761
  rem.onclick = () => {
3598
3762
  if (countItems() > min) {
3599
3763
  item.remove();
@@ -3606,6 +3770,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3606
3770
  }
3607
3771
  }
3608
3772
  containerWrap.appendChild(itemsWrap);
3773
+ if (!state.config.readonly) {
3774
+ containerWrap.appendChild(createAddButton());
3775
+ }
3609
3776
  updateAddButton();
3610
3777
  wrapper.appendChild(containerWrap);
3611
3778
  }
@@ -3658,6 +3825,27 @@ function validateContainerElement(element, key, context) {
3658
3825
  `[data-container-item="${key}[${i}]"]`
3659
3826
  ) || scopeRoot;
3660
3827
  element.elements.forEach((child) => {
3828
+ var _a;
3829
+ if (child.enableIf) {
3830
+ try {
3831
+ const rootFormData = ((_a = context.instance) == null ? void 0 : _a.getState().formRoot) ? extractRootFormData(context.instance.getState().formRoot) : {};
3832
+ const shouldEnable = evaluateEnableCondition(
3833
+ child.enableIf,
3834
+ rootFormData,
3835
+ // Root form data for absolute scope
3836
+ itemData
3837
+ // Container data for relative scope
3838
+ );
3839
+ if (!shouldEnable) {
3840
+ return;
3841
+ }
3842
+ } catch (error) {
3843
+ console.error(
3844
+ `Error evaluating enableIf for field "${child.key}" in container "${key}[${i}]":`,
3845
+ error
3846
+ );
3847
+ }
3848
+ }
3661
3849
  if (child.hidden || child.type === "hidden") {
3662
3850
  itemData[child.key] = child.default !== void 0 ? child.default : null;
3663
3851
  } else {
@@ -3677,6 +3865,27 @@ function validateContainerElement(element, key, context) {
3677
3865
  const containerData = {};
3678
3866
  const containerContainer = scopeRoot.querySelector(`[data-container="${key}"]`) || scopeRoot;
3679
3867
  element.elements.forEach((child) => {
3868
+ var _a;
3869
+ if (child.enableIf) {
3870
+ try {
3871
+ const rootFormData = ((_a = context.instance) == null ? void 0 : _a.getState().formRoot) ? extractRootFormData(context.instance.getState().formRoot) : {};
3872
+ const shouldEnable = evaluateEnableCondition(
3873
+ child.enableIf,
3874
+ rootFormData,
3875
+ // Root form data for absolute scope
3876
+ containerData
3877
+ // Container data for relative scope
3878
+ );
3879
+ if (!shouldEnable) {
3880
+ return;
3881
+ }
3882
+ } catch (error) {
3883
+ console.error(
3884
+ `Error evaluating enableIf for field "${child.key}" in container "${key}":`,
3885
+ error
3886
+ );
3887
+ }
3888
+ }
3680
3889
  if (child.hidden || child.type === "hidden") {
3681
3890
  containerData[child.key] = child.default !== void 0 ? child.default : null;
3682
3891
  } else {
@@ -3863,32 +4072,183 @@ if (typeof document !== "undefined") {
3863
4072
  }
3864
4073
  });
3865
4074
  }
3866
- function checkDisplayCondition(element, ctx) {
3867
- var _a;
3868
- if (!element.displayIf) {
3869
- return null;
4075
+ function shouldDisableElement(element, ctx) {
4076
+ var _a, _b;
4077
+ if (!element.enableIf) {
4078
+ return false;
3870
4079
  }
3871
4080
  try {
3872
- const dataForCondition = (_a = ctx.formData) != null ? _a : ctx.prefill;
3873
- const shouldDisplay = evaluateDisplayCondition(
3874
- element.displayIf,
3875
- dataForCondition
4081
+ const rootFormData = (_b = (_a = ctx.formData) != null ? _a : ctx.prefill) != null ? _b : {};
4082
+ const containerData = ctx.path ? getValueByPath(rootFormData, ctx.path) : void 0;
4083
+ const shouldEnable = evaluateEnableCondition(
4084
+ element.enableIf,
4085
+ rootFormData,
4086
+ containerData
3876
4087
  );
3877
- if (!shouldDisplay) {
3878
- const hiddenWrapper = document.createElement("div");
3879
- hiddenWrapper.className = "fb-field-wrapper-hidden";
3880
- hiddenWrapper.style.display = "none";
3881
- hiddenWrapper.setAttribute("data-field-key", element.key);
3882
- hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3883
- return hiddenWrapper;
3884
- }
4088
+ return !shouldEnable;
3885
4089
  } catch (error) {
3886
4090
  console.error(
3887
- `Error evaluating displayIf for field "${element.key}":`,
4091
+ `Error evaluating enableIf for field "${element.key}":`,
3888
4092
  error
3889
4093
  );
3890
4094
  }
3891
- return null;
4095
+ return false;
4096
+ }
4097
+ function extractDOMValue(fieldPath, formRoot) {
4098
+ const input = formRoot.querySelector(
4099
+ `[name="${fieldPath}"]`
4100
+ );
4101
+ if (!input) {
4102
+ return void 0;
4103
+ }
4104
+ if (input instanceof HTMLSelectElement) {
4105
+ return input.value;
4106
+ } else if (input instanceof HTMLInputElement) {
4107
+ if (input.type === "checkbox") {
4108
+ return input.checked;
4109
+ } else if (input.type === "radio") {
4110
+ const checked = formRoot.querySelector(
4111
+ `[name="${fieldPath}"]:checked`
4112
+ );
4113
+ return checked ? checked.value : void 0;
4114
+ } else {
4115
+ return input.value;
4116
+ }
4117
+ } else if (input instanceof HTMLTextAreaElement) {
4118
+ return input.value;
4119
+ }
4120
+ return void 0;
4121
+ }
4122
+ function reevaluateEnableIf(wrapper, element, ctx) {
4123
+ var _a, _b;
4124
+ if (!element.enableIf) {
4125
+ return;
4126
+ }
4127
+ const formRoot = ctx.state.formRoot;
4128
+ if (!formRoot) {
4129
+ console.error(`Cannot re-evaluate enableIf: formRoot is null`);
4130
+ return;
4131
+ }
4132
+ const condition = element.enableIf;
4133
+ const scope = (_a = condition.scope) != null ? _a : "relative";
4134
+ let rootFormData = {};
4135
+ const containerData = {};
4136
+ const effectiveScope = !ctx.path || ctx.path === "" ? "absolute" : scope;
4137
+ if (effectiveScope === "relative" && ctx.path) {
4138
+ const containerMatch = ctx.path.match(/^(.+)\[(\d+)\]$/);
4139
+ if (containerMatch) {
4140
+ const containerKey = containerMatch[1];
4141
+ const containerIndex = parseInt(containerMatch[2], 10);
4142
+ const containerItemElement = formRoot.querySelector(
4143
+ `[data-container-item="${containerKey}[${containerIndex}]"]`
4144
+ );
4145
+ if (containerItemElement) {
4146
+ const inputs = containerItemElement.querySelectorAll(
4147
+ "input, select, textarea"
4148
+ );
4149
+ inputs.forEach((input) => {
4150
+ const fieldName = input.getAttribute("name");
4151
+ if (fieldName) {
4152
+ const fieldKeyMatch = fieldName.match(/\.([^.[\]]+)$/);
4153
+ if (fieldKeyMatch) {
4154
+ const fieldKey = fieldKeyMatch[1];
4155
+ if (input instanceof HTMLSelectElement) {
4156
+ containerData[fieldKey] = input.value;
4157
+ } else if (input instanceof HTMLInputElement) {
4158
+ if (input.type === "checkbox") {
4159
+ containerData[fieldKey] = input.checked;
4160
+ } else if (input.type === "radio") {
4161
+ if (input.checked) {
4162
+ containerData[fieldKey] = input.value;
4163
+ }
4164
+ } else {
4165
+ containerData[fieldKey] = input.value;
4166
+ }
4167
+ } else if (input instanceof HTMLTextAreaElement) {
4168
+ containerData[fieldKey] = input.value;
4169
+ }
4170
+ }
4171
+ }
4172
+ });
4173
+ }
4174
+ }
4175
+ } else {
4176
+ const dependencyKey = condition.key;
4177
+ const dependencyValue = extractDOMValue(dependencyKey, formRoot);
4178
+ if (dependencyValue !== void 0) {
4179
+ rootFormData[dependencyKey] = dependencyValue;
4180
+ } else {
4181
+ rootFormData = (_b = ctx.formData) != null ? _b : ctx.prefill;
4182
+ }
4183
+ }
4184
+ try {
4185
+ const shouldEnable = evaluateEnableCondition(
4186
+ condition,
4187
+ rootFormData,
4188
+ containerData
4189
+ );
4190
+ if (shouldEnable) {
4191
+ wrapper.style.display = "";
4192
+ wrapper.classList.remove("fb-field-wrapper-disabled");
4193
+ wrapper.removeAttribute("data-conditionally-disabled");
4194
+ } else {
4195
+ wrapper.style.display = "none";
4196
+ wrapper.classList.add("fb-field-wrapper-disabled");
4197
+ wrapper.setAttribute("data-conditionally-disabled", "true");
4198
+ }
4199
+ } catch (error) {
4200
+ console.error(`Error re-evaluating enableIf for field "${element.key}":`, error);
4201
+ }
4202
+ }
4203
+ function setupEnableIfListeners(wrapper, element, ctx) {
4204
+ var _a;
4205
+ if (!element.enableIf) {
4206
+ return;
4207
+ }
4208
+ const formRoot = ctx.state.formRoot;
4209
+ if (!formRoot) {
4210
+ console.error(`Cannot setup enableIf listeners: formRoot is null`);
4211
+ return;
4212
+ }
4213
+ const condition = element.enableIf;
4214
+ const scope = (_a = condition.scope) != null ? _a : "relative";
4215
+ const dependencyKey = condition.key;
4216
+ let dependencyFieldPath;
4217
+ if (scope === "relative" && ctx.path) {
4218
+ dependencyFieldPath = `${ctx.path}.${dependencyKey}`;
4219
+ } else {
4220
+ dependencyFieldPath = dependencyKey;
4221
+ }
4222
+ const dependencyInput = formRoot.querySelector(
4223
+ `[name="${dependencyFieldPath}"]`
4224
+ );
4225
+ if (!dependencyInput) {
4226
+ const observer = new MutationObserver(() => {
4227
+ const input = formRoot.querySelector(
4228
+ `[name="${dependencyFieldPath}"]`
4229
+ );
4230
+ if (input) {
4231
+ input.addEventListener("change", () => {
4232
+ reevaluateEnableIf(wrapper, element, ctx);
4233
+ });
4234
+ input.addEventListener("input", () => {
4235
+ reevaluateEnableIf(wrapper, element, ctx);
4236
+ });
4237
+ observer.disconnect();
4238
+ }
4239
+ });
4240
+ observer.observe(formRoot, {
4241
+ childList: true,
4242
+ subtree: true
4243
+ });
4244
+ return;
4245
+ }
4246
+ dependencyInput.addEventListener("change", () => {
4247
+ reevaluateEnableIf(wrapper, element, ctx);
4248
+ });
4249
+ dependencyInput.addEventListener("input", () => {
4250
+ reevaluateEnableIf(wrapper, element, ctx);
4251
+ });
3892
4252
  }
3893
4253
  function createFieldLabel(element) {
3894
4254
  const title = document.createElement("label");
@@ -4006,10 +4366,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
4006
4366
  }
4007
4367
  }
4008
4368
  function renderElement2(element, ctx) {
4009
- const hiddenElement = checkDisplayCondition(element, ctx);
4010
- if (hiddenElement) {
4011
- return hiddenElement;
4012
- }
4369
+ const initiallyDisabled = shouldDisableElement(element, ctx);
4013
4370
  const wrapper = document.createElement("div");
4014
4371
  wrapper.className = "mb-6 fb-field-wrapper";
4015
4372
  wrapper.setAttribute("data-field-key", element.key);
@@ -4017,6 +4374,12 @@ function renderElement2(element, ctx) {
4017
4374
  wrapper.appendChild(label);
4018
4375
  const pathKey = pathJoin(ctx.path, element.key);
4019
4376
  dispatchToRenderer(element, ctx, wrapper, pathKey);
4377
+ if (initiallyDisabled) {
4378
+ wrapper.style.display = "none";
4379
+ wrapper.classList.add("fb-field-wrapper-disabled");
4380
+ wrapper.setAttribute("data-conditionally-disabled", "true");
4381
+ }
4382
+ setupEnableIfListeners(wrapper, element, ctx);
4020
4383
  return wrapper;
4021
4384
  }
4022
4385
  setRenderElement(renderElement2);
@@ -4675,15 +5038,16 @@ var FormBuilderInstance = class {
4675
5038
  event.preventDefault();
4676
5039
  event.stopPropagation();
4677
5040
  const hintValuesJson = target.getAttribute("data-hint-values");
5041
+ const isRootHint = target.getAttribute("data-root-hint") === "true";
4678
5042
  const containerKey = target.getAttribute("data-container-key");
4679
- if (!hintValuesJson || !containerKey) {
5043
+ if (!hintValuesJson || !isRootHint && !containerKey) {
4680
5044
  console.warn("Prefill hint missing required data attributes");
4681
5045
  return;
4682
5046
  }
4683
5047
  try {
4684
5048
  const hintValues = JSON.parse(hintValuesJson);
4685
5049
  for (const fieldKey in hintValues) {
4686
- const fullPath = `${containerKey}.${fieldKey}`;
5050
+ const fullPath = isRootHint ? fieldKey : `${containerKey}.${fieldKey}`;
4687
5051
  const value = hintValues[fieldKey];
4688
5052
  this.updateField(fullPath, value);
4689
5053
  }
@@ -4691,6 +5055,27 @@ var FormBuilderInstance = class {
4691
5055
  console.error("Error parsing prefill hint values:", error);
4692
5056
  }
4693
5057
  }
5058
+ /**
5059
+ * Create root-level prefill hints UI
5060
+ */
5061
+ createRootPrefillHints(hints) {
5062
+ const hintsContainer = document.createElement("div");
5063
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
5064
+ hints.forEach((hint) => {
5065
+ const hintButton = document.createElement("button");
5066
+ hintButton.type = "button";
5067
+ hintButton.className = "fb-prefill-hint";
5068
+ if (hint.icon) {
5069
+ hintButton.textContent = `${hint.icon} ${hint.label}`;
5070
+ } else {
5071
+ hintButton.textContent = hint.label;
5072
+ }
5073
+ hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
5074
+ hintButton.setAttribute("data-root-hint", "true");
5075
+ hintsContainer.appendChild(hintButton);
5076
+ });
5077
+ return hintsContainer;
5078
+ }
4694
5079
  /**
4695
5080
  * Render form from schema
4696
5081
  */
@@ -4706,8 +5091,19 @@ var FormBuilderInstance = class {
4706
5091
  clear(root);
4707
5092
  root.setAttribute("data-fb-root", "true");
4708
5093
  injectThemeVariables(root, this.state.config.theme);
4709
- const formEl = document.createElement("div");
4710
- formEl.className = "space-y-6";
5094
+ const rootContainer = document.createElement("div");
5095
+ rootContainer.className = "space-y-6";
5096
+ if (schema.prefillHints && !this.state.config.readonly) {
5097
+ const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
5098
+ rootContainer.appendChild(hintsContainer);
5099
+ }
5100
+ const fieldsWrapper = document.createElement("div");
5101
+ const columns = schema.columns || 1;
5102
+ if (columns === 1) {
5103
+ fieldsWrapper.className = "space-y-4";
5104
+ } else {
5105
+ fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
5106
+ }
4711
5107
  schema.elements.forEach((element) => {
4712
5108
  if (element.hidden) {
4713
5109
  return;
@@ -4716,13 +5112,14 @@ var FormBuilderInstance = class {
4716
5112
  path: "",
4717
5113
  prefill: prefill || {},
4718
5114
  formData: prefill || {},
4719
- // Pass complete root data for displayIf evaluation
5115
+ // Pass complete root data for enableIf evaluation
4720
5116
  state: this.state,
4721
5117
  instance: this
4722
5118
  });
4723
- formEl.appendChild(block);
5119
+ fieldsWrapper.appendChild(block);
4724
5120
  });
4725
- root.appendChild(formEl);
5121
+ rootContainer.appendChild(fieldsWrapper);
5122
+ root.appendChild(rootContainer);
4726
5123
  if (!this.state.config.readonly) {
4727
5124
  root.addEventListener("click", this.handlePrefillHintClick.bind(this));
4728
5125
  }
@@ -4764,15 +5161,18 @@ var FormBuilderInstance = class {
4764
5161
  };
4765
5162
  setValidateElement(validateElement2);
4766
5163
  this.state.schema.elements.forEach((element) => {
4767
- if (element.displayIf) {
5164
+ if (element.enableIf) {
4768
5165
  try {
4769
- const shouldDisplay = evaluateDisplayCondition(element.displayIf, data);
4770
- if (!shouldDisplay) {
5166
+ const shouldEnable = evaluateEnableCondition(
5167
+ element.enableIf,
5168
+ data
5169
+ );
5170
+ if (!shouldEnable) {
4771
5171
  return;
4772
5172
  }
4773
5173
  } catch (error) {
4774
5174
  console.error(
4775
- `Error evaluating displayIf for field "${element.key}" during validation:`,
5175
+ `Error evaluating enableIf for field "${element.key}" during validation:`,
4776
5176
  error
4777
5177
  );
4778
5178
  }
@@ -4860,7 +5260,9 @@ var FormBuilderInstance = class {
4860
5260
  }
4861
5261
  if (element.type === "container" || element.type === "group") {
4862
5262
  const containerElement = element;
4863
- const nestedData = this.buildHiddenFieldsData(containerElement.elements);
5263
+ const nestedData = this.buildHiddenFieldsData(
5264
+ containerElement.elements
5265
+ );
4864
5266
  if (Object.keys(nestedData).length > 0) {
4865
5267
  if (!(key in data)) {
4866
5268
  data[key] = nestedData;
@@ -4878,7 +5280,9 @@ var FormBuilderInstance = class {
4878
5280
  */
4879
5281
  setFormData(data) {
4880
5282
  if (!this.state.schema || !this.state.formRoot) {
4881
- console.warn("setFormData: Form not initialized. Call renderForm() first.");
5283
+ console.warn(
5284
+ "setFormData: Form not initialized. Call renderForm() first."
5285
+ );
4882
5286
  return;
4883
5287
  }
4884
5288
  for (const fieldPath in data) {
@@ -4892,20 +5296,27 @@ var FormBuilderInstance = class {
4892
5296
  */
4893
5297
  updateField(fieldPath, value) {
4894
5298
  if (!this.state.schema || !this.state.formRoot) {
4895
- console.warn("updateField: Form not initialized. Call renderForm() first.");
5299
+ console.warn(
5300
+ "updateField: Form not initialized. Call renderForm() first."
5301
+ );
4896
5302
  return;
4897
5303
  }
4898
5304
  const schemaElement = this.findSchemaElement(fieldPath);
4899
5305
  if (!schemaElement) {
4900
- console.warn(`updateField: Schema element not found for path "${fieldPath}"`);
5306
+ console.warn(
5307
+ `updateField: Schema element not found for path "${fieldPath}"`
5308
+ );
4901
5309
  return;
4902
5310
  }
4903
5311
  const domElement = this.findFormElementByFieldPath(fieldPath);
4904
5312
  if (!domElement) {
4905
- console.warn(`updateField: DOM element not found for path "${fieldPath}"`);
5313
+ console.warn(
5314
+ `updateField: DOM element not found for path "${fieldPath}"`
5315
+ );
4906
5316
  return;
4907
5317
  }
4908
5318
  this.updateFieldValue(domElement, schemaElement, fieldPath, value);
5319
+ this.reevaluateConditionalFields();
4909
5320
  if (this.state.config.onChange || this.state.config.onFieldChange) {
4910
5321
  this.triggerOnChange(fieldPath, value);
4911
5322
  }
@@ -4934,7 +5345,7 @@ var FormBuilderInstance = class {
4934
5345
  }
4935
5346
  }
4936
5347
  /**
4937
- * Re-evaluate all conditional fields (displayIf) based on current form data
5348
+ * Re-evaluate all conditional fields (enableIf) based on current form data
4938
5349
  * This is called automatically when form data changes (via onChange events)
4939
5350
  */
4940
5351
  reevaluateConditionalFields() {
@@ -4942,47 +5353,82 @@ var FormBuilderInstance = class {
4942
5353
  const formData = this.validateForm(true).data;
4943
5354
  const checkElements = (elements, currentPath) => {
4944
5355
  elements.forEach((element) => {
5356
+ var _a, _b, _c;
4945
5357
  const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
4946
- if (element.displayIf) {
4947
- const fieldWrappers = this.state.formRoot.querySelectorAll(
4948
- `[data-field-key="${element.key}"]`
4949
- );
4950
- fieldWrappers.forEach((wrapper) => {
4951
- var _a, _b;
5358
+ if (element.enableIf) {
5359
+ let fieldWrapper = null;
5360
+ if (currentPath) {
5361
+ const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
5362
+ if (pathMatch) {
5363
+ const containerKey = pathMatch[1];
5364
+ const containerIndex = pathMatch[2];
5365
+ const containerElement = this.state.formRoot.querySelector(
5366
+ `[data-container-item="${containerKey}[${containerIndex}]"]`
5367
+ );
5368
+ if (containerElement) {
5369
+ fieldWrapper = containerElement.querySelector(
5370
+ `[data-field-key="${element.key}"]`
5371
+ );
5372
+ }
5373
+ } else {
5374
+ const containerElement = this.state.formRoot.querySelector(
5375
+ `[data-container="${currentPath}"]`
5376
+ );
5377
+ if (containerElement) {
5378
+ fieldWrapper = containerElement.querySelector(
5379
+ `[data-field-key="${element.key}"]`
5380
+ );
5381
+ }
5382
+ }
5383
+ } else {
5384
+ fieldWrapper = this.state.formRoot.querySelector(
5385
+ `[data-field-key="${element.key}"]`
5386
+ );
5387
+ }
5388
+ if (fieldWrapper) {
5389
+ const wrapper = fieldWrapper;
4952
5390
  try {
4953
- const shouldDisplay = evaluateDisplayCondition(
4954
- element.displayIf,
4955
- formData
4956
- // Use complete formData for condition evaluation
5391
+ let containerData = void 0;
5392
+ const scope = (_a = element.enableIf.scope) != null ? _a : "relative";
5393
+ if (scope === "relative" && currentPath) {
5394
+ containerData = getValueByPath(formData, currentPath);
5395
+ }
5396
+ const shouldEnable = evaluateEnableCondition(
5397
+ element.enableIf,
5398
+ formData,
5399
+ // Use complete formData for absolute scope
5400
+ containerData
5401
+ // Use container-specific data for relative scope
4957
5402
  );
4958
- const isCurrentlyHidden = wrapper.getAttribute("data-conditionally-hidden") === "true";
4959
- if (shouldDisplay && isCurrentlyHidden) {
5403
+ const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
5404
+ if (shouldEnable && isCurrentlyDisabled) {
5405
+ const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
5406
+ const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
4960
5407
  const newWrapper = renderElement2(element, {
4961
- path: fullPath,
4962
- // Use accumulated path
4963
- prefill: formData,
4964
- // Use complete formData for root-level elements
5408
+ path: currentPath,
5409
+ // Use container path (empty string for root-level)
5410
+ prefill: prefillContext,
4965
5411
  formData,
4966
- // Pass complete formData for displayIf evaluation
5412
+ // Pass complete formData for enableIf evaluation
4967
5413
  state: this.state,
4968
5414
  instance: this
4969
5415
  });
4970
- (_a = wrapper.parentNode) == null ? void 0 : _a.replaceChild(newWrapper, wrapper);
4971
- } else if (!shouldDisplay && !isCurrentlyHidden) {
4972
- const hiddenWrapper = document.createElement("div");
4973
- hiddenWrapper.className = "fb-field-wrapper-hidden";
4974
- hiddenWrapper.style.display = "none";
4975
- hiddenWrapper.setAttribute("data-field-key", element.key);
4976
- hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
4977
- (_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(hiddenWrapper, wrapper);
5416
+ (_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(newWrapper, wrapper);
5417
+ } else if (!shouldEnable && !isCurrentlyDisabled) {
5418
+ const disabledWrapper = document.createElement("div");
5419
+ disabledWrapper.className = "fb-field-wrapper-disabled";
5420
+ disabledWrapper.style.display = "none";
5421
+ disabledWrapper.setAttribute("data-field-key", element.key);
5422
+ disabledWrapper.setAttribute("data-conditionally-disabled", "true");
5423
+ (_c = wrapper.parentNode) == null ? void 0 : _c.replaceChild(disabledWrapper, wrapper);
4978
5424
  }
4979
5425
  } catch (error) {
4980
5426
  console.error(
4981
- `Error re-evaluating displayIf for field "${element.key}":`,
5427
+ `Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
4982
5428
  error
4983
5429
  );
4984
5430
  }
4985
- });
5431
+ }
4986
5432
  }
4987
5433
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
4988
5434
  const containerData = formData == null ? void 0 : formData[element.key];