@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.
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.displayIf) {
73
- const displayIf = element.displayIf;
74
- if (!displayIf.key || typeof displayIf.key !== "string") {
107
+ if (element.enableIf) {
108
+ const enableIf = element.enableIf;
109
+ if (!enableIf.key || typeof enableIf.key !== "string") {
75
110
  errors.push(
76
- `${elementPath}: displayIf must have a 'key' property of type string`
111
+ `${elementPath}: enableIf must have a 'key' property of type string`
77
112
  );
78
113
  }
79
- const hasOperator = "equals" in displayIf;
114
+ const hasOperator = "equals" in enableIf;
80
115
  if (!hasOperator) {
81
116
  errors.push(
82
- `${elementPath}: displayIf must have at least one operator (equals, etc.)`
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/display-conditions.ts
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 evaluateDisplayCondition(condition, formData) {
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
- "Invalid displayIf condition: must have a 'key' property"
233
+ `Invalid enableIf scope: must be "relative" or "absolute" (got "${scope}")`
190
234
  );
191
235
  }
192
- const actualValue = getValueByPath(formData, condition.key);
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 displayIf condition: no recognized operator (equals, etc.)`
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 displayIf comparison, using reference equality"
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
- ctx.instance.triggerOnChange(pathKey, textInput.value);
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
- ctx.instance.triggerOnChange(textInput.name, textInput.value);
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);
@@ -410,7 +456,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
410
456
  font-size: var(--fb-font-size);
411
457
  transition: all var(--fb-transition-duration);
412
458
  `;
413
- addBtn.textContent = `+ Add ${element.label || "Text"}`;
459
+ addBtn.textContent = "+";
414
460
  addBtn.addEventListener("mouseenter", () => {
415
461
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
416
462
  });
@@ -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
- values.push(val);
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 = values.filter((v) => v.trim() !== "");
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: "", errors };
583
+ return { value: null, errors };
540
584
  }
541
- validateTextInput(input, val, key);
542
- return { value: val, errors };
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
- ctx.instance.triggerOnChange(pathKey, textareaInput.value);
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
- ctx.instance.triggerOnChange(textareaInput.name, textareaInput.value);
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);
@@ -659,7 +703,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
659
703
  removeBtn = document.createElement("button");
660
704
  removeBtn.type = "button";
661
705
  removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
662
- removeBtn.innerHTML = "\u2715 Remove";
706
+ removeBtn.innerHTML = "\u2715";
663
707
  removeBtn.onclick = () => {
664
708
  const currentIndex = Array.from(container.children).indexOf(
665
709
  item
@@ -687,7 +731,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
687
731
  const addBtn = document.createElement("button");
688
732
  addBtn.type = "button";
689
733
  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";
690
- addBtn.textContent = `+ Add ${element.label || "Textarea"}`;
734
+ addBtn.textContent = "+";
691
735
  addBtn.onclick = () => {
692
736
  values.push(element.default || "");
693
737
  addTextareaItem(element.default || "");
@@ -829,7 +873,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
829
873
  const addBtn = document.createElement("button");
830
874
  addBtn.type = "button";
831
875
  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";
832
- addBtn.textContent = `+ Add ${element.label || "Number"}`;
876
+ addBtn.textContent = "+";
833
877
  addBtn.onclick = () => {
834
878
  values.push(element.default || "");
835
879
  addNumberItem(element.default || "");
@@ -1111,7 +1155,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1111
1155
  const addBtn = document.createElement("button");
1112
1156
  addBtn.type = "button";
1113
1157
  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";
1114
- addBtn.textContent = `+ Add ${element.label || "Selection"}`;
1158
+ addBtn.textContent = "+";
1115
1159
  addBtn.onclick = () => {
1116
1160
  const defaultValue = element.default || element.options?.[0]?.value || "";
1117
1161
  values.push(defaultValue);
@@ -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(".change-file-btn");
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(".delete-file-btn");
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(document.createTextNode("Your browser does not support the video tag."));
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(container, resourceId, fileName, meta, state);
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(document.createTextNode(` ${t("dragDropText", state)}`));
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(files[0], fileContainer, pathKey, state, deps, ctx.instance);
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(picker.files[0], fileContainer, pathKey, state, deps, ctx.instance);
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(filesContainer, initialFiles, state, updateFilesList2, pathKey, ctx.instance);
2162
- setupFilesPickerHandler(filesPicker, initialFiles, state, updateFilesList2, pathKey, ctx.instance);
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(filesContainer, initialFiles, state, updateFilesDisplay, pathKey, ctx.instance);
2225
- setupFilesPickerHandler(filesPicker, initialFiles, state, updateFilesDisplay, pathKey, ctx.instance);
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
  }
@@ -2621,7 +2721,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2621
2721
  font-size: var(--fb-font-size);
2622
2722
  transition: all var(--fb-transition-duration);
2623
2723
  `;
2624
- addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2724
+ addBtn.textContent = "+";
2625
2725
  addBtn.addEventListener("mouseenter", () => {
2626
2726
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2627
2727
  });
@@ -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(".colour-picker-hidden");
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(".colour-picker-hidden");
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(
@@ -3080,7 +3172,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
3080
3172
  font-size: var(--fb-font-size);
3081
3173
  transition: all var(--fb-transition-duration);
3082
3174
  `;
3083
- addBtn.textContent = `+ Add ${element.label || "Slider"}`;
3175
+ addBtn.textContent = "+";
3084
3176
  addBtn.addEventListener("mouseenter", () => {
3085
3177
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3086
3178
  });
@@ -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(errorElement, sliderContainer.nextSibling);
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 displayIf evaluation
3497
+ // Complete root data for enableIf evaluation
3376
3498
  state: ctx.state
3377
3499
  };
3378
3500
  element.elements.forEach((child) => {
@@ -3392,15 +3514,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3392
3514
  header.className = "flex justify-between items-center mb-4";
3393
3515
  const left = document.createElement("div");
3394
3516
  left.className = "flex-1";
3395
- const right = document.createElement("div");
3396
- right.className = "flex gap-2";
3397
3517
  const itemsWrap = document.createElement("div");
3398
3518
  itemsWrap.className = "space-y-4";
3399
3519
  containerWrap.appendChild(header);
3400
3520
  header.appendChild(left);
3401
- if (!state.config.readonly) {
3402
- header.appendChild(right);
3403
- }
3404
3521
  if (!ctx.state.config.readonly) {
3405
3522
  const hintsElement = createPrefillHints(element, element.key);
3406
3523
  if (hintsElement) {
@@ -3414,17 +3531,31 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3414
3531
  const createAddButton = () => {
3415
3532
  const add = document.createElement("button");
3416
3533
  add.type = "button";
3417
- add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
3418
- add.textContent = t("addElement", state);
3534
+ add.className = "add-container-btn mt-2 px-3 py-1 rounded";
3535
+ add.style.cssText = `
3536
+ color: var(--fb-primary-color);
3537
+ border: var(--fb-border-width) solid var(--fb-primary-color);
3538
+ background-color: transparent;
3539
+ font-size: var(--fb-font-size);
3540
+ transition: all var(--fb-transition-duration);
3541
+ `;
3542
+ add.textContent = "+";
3543
+ add.addEventListener("mouseenter", () => {
3544
+ add.style.backgroundColor = "var(--fb-background-hover-color)";
3545
+ });
3546
+ add.addEventListener("mouseleave", () => {
3547
+ add.style.backgroundColor = "transparent";
3548
+ });
3419
3549
  add.onclick = () => {
3420
3550
  if (countItems() < max) {
3421
3551
  const idx = countItems();
3552
+ const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
3422
3553
  const subCtx = {
3423
3554
  state: ctx.state,
3424
3555
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3425
3556
  prefill: {},
3426
- formData: ctx.formData ?? ctx.prefill
3427
- // Complete root data for displayIf
3557
+ formData: currentFormData
3558
+ // Current root data from DOM for enableIf
3428
3559
  };
3429
3560
  const item = document.createElement("div");
3430
3561
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3445,8 +3576,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3445
3576
  if (!state.config.readonly) {
3446
3577
  const rem = document.createElement("button");
3447
3578
  rem.type = "button";
3448
- 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";
3449
- rem.textContent = "\xD7";
3579
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3580
+ rem.style.cssText = `
3581
+ color: var(--fb-error-color);
3582
+ background-color: transparent;
3583
+ transition: background-color var(--fb-transition-duration);
3584
+ `;
3585
+ rem.textContent = "\u2715";
3586
+ rem.addEventListener("mouseenter", () => {
3587
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3588
+ });
3589
+ rem.addEventListener("mouseleave", () => {
3590
+ rem.style.backgroundColor = "transparent";
3591
+ });
3450
3592
  rem.onclick = () => {
3451
3593
  item.remove();
3452
3594
  updateAddButton();
@@ -3462,16 +3604,16 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3462
3604
  };
3463
3605
  const updateAddButton = () => {
3464
3606
  const currentCount = countItems();
3465
- const addBtn = right.querySelector("button");
3466
- if (addBtn) {
3467
- addBtn.disabled = currentCount >= max;
3468
- addBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3607
+ const existingAddBtn = containerWrap.querySelector(
3608
+ ".add-container-btn"
3609
+ );
3610
+ if (existingAddBtn) {
3611
+ existingAddBtn.disabled = currentCount >= max;
3612
+ existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3613
+ existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
3469
3614
  }
3470
3615
  left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? "\u221E" : max})</span>`;
3471
3616
  };
3472
- if (!state.config.readonly) {
3473
- right.appendChild(createAddButton());
3474
- }
3475
3617
  if (pre && Array.isArray(pre)) {
3476
3618
  pre.forEach((prefillObj, idx) => {
3477
3619
  const subCtx = {
@@ -3479,7 +3621,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3479
3621
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3480
3622
  prefill: prefillObj || {},
3481
3623
  formData: ctx.formData ?? ctx.prefill
3482
- // Complete root data for displayIf
3624
+ // Complete root data for enableIf
3483
3625
  };
3484
3626
  const item = document.createElement("div");
3485
3627
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3500,8 +3642,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3500
3642
  if (!state.config.readonly) {
3501
3643
  const rem = document.createElement("button");
3502
3644
  rem.type = "button";
3503
- 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";
3504
- rem.textContent = "\xD7";
3645
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3646
+ rem.style.cssText = `
3647
+ color: var(--fb-error-color);
3648
+ background-color: transparent;
3649
+ transition: background-color var(--fb-transition-duration);
3650
+ `;
3651
+ rem.textContent = "\u2715";
3652
+ rem.addEventListener("mouseenter", () => {
3653
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3654
+ });
3655
+ rem.addEventListener("mouseleave", () => {
3656
+ rem.style.backgroundColor = "transparent";
3657
+ });
3505
3658
  rem.onclick = () => {
3506
3659
  item.remove();
3507
3660
  updateAddButton();
@@ -3520,7 +3673,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3520
3673
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3521
3674
  prefill: {},
3522
3675
  formData: ctx.formData ?? ctx.prefill
3523
- // Complete root data for displayIf
3676
+ // Complete root data for enableIf
3524
3677
  };
3525
3678
  const item = document.createElement("div");
3526
3679
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -3540,8 +3693,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3540
3693
  item.appendChild(childWrapper);
3541
3694
  const rem = document.createElement("button");
3542
3695
  rem.type = "button";
3543
- 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";
3544
- rem.textContent = "\xD7";
3696
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3697
+ rem.style.cssText = `
3698
+ color: var(--fb-error-color);
3699
+ background-color: transparent;
3700
+ transition: background-color var(--fb-transition-duration);
3701
+ `;
3702
+ rem.textContent = "\u2715";
3703
+ rem.addEventListener("mouseenter", () => {
3704
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3705
+ });
3706
+ rem.addEventListener("mouseleave", () => {
3707
+ rem.style.backgroundColor = "transparent";
3708
+ });
3545
3709
  rem.onclick = () => {
3546
3710
  if (countItems() > min) {
3547
3711
  item.remove();
@@ -3554,6 +3718,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3554
3718
  }
3555
3719
  }
3556
3720
  containerWrap.appendChild(itemsWrap);
3721
+ if (!state.config.readonly) {
3722
+ containerWrap.appendChild(createAddButton());
3723
+ }
3557
3724
  updateAddButton();
3558
3725
  wrapper.appendChild(containerWrap);
3559
3726
  }
@@ -3605,6 +3772,26 @@ function validateContainerElement(element, key, context) {
3605
3772
  `[data-container-item="${key}[${i}]"]`
3606
3773
  ) || scopeRoot;
3607
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
+ }
3608
3795
  if (child.hidden || child.type === "hidden") {
3609
3796
  itemData[child.key] = child.default !== void 0 ? child.default : null;
3610
3797
  } else {
@@ -3624,6 +3811,26 @@ function validateContainerElement(element, key, context) {
3624
3811
  const containerData = {};
3625
3812
  const containerContainer = scopeRoot.querySelector(`[data-container="${key}"]`) || scopeRoot;
3626
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
+ }
3627
3834
  if (child.hidden || child.type === "hidden") {
3628
3835
  containerData[child.key] = child.default !== void 0 ? child.default : null;
3629
3836
  } else {
@@ -3808,31 +4015,180 @@ if (typeof document !== "undefined") {
3808
4015
  }
3809
4016
  });
3810
4017
  }
3811
- function checkDisplayCondition(element, ctx) {
3812
- if (!element.displayIf) {
3813
- return null;
4018
+ function shouldDisableElement(element, ctx) {
4019
+ if (!element.enableIf) {
4020
+ return false;
3814
4021
  }
3815
4022
  try {
3816
- const dataForCondition = ctx.formData ?? ctx.prefill;
3817
- const shouldDisplay = evaluateDisplayCondition(
3818
- element.displayIf,
3819
- dataForCondition
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
3820
4029
  );
3821
- if (!shouldDisplay) {
3822
- const hiddenWrapper = document.createElement("div");
3823
- hiddenWrapper.className = "fb-field-wrapper-hidden";
3824
- hiddenWrapper.style.display = "none";
3825
- hiddenWrapper.setAttribute("data-field-key", element.key);
3826
- hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3827
- return hiddenWrapper;
3828
- }
4030
+ return !shouldEnable;
3829
4031
  } catch (error) {
3830
4032
  console.error(
3831
- `Error evaluating displayIf for field "${element.key}":`,
4033
+ `Error evaluating enableIf for field "${element.key}":`,
3832
4034
  error
3833
4035
  );
3834
4036
  }
3835
- return null;
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
+ });
3836
4192
  }
3837
4193
  function createFieldLabel(element) {
3838
4194
  const title = document.createElement("label");
@@ -3950,10 +4306,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3950
4306
  }
3951
4307
  }
3952
4308
  function renderElement2(element, ctx) {
3953
- const hiddenElement = checkDisplayCondition(element, ctx);
3954
- if (hiddenElement) {
3955
- return hiddenElement;
3956
- }
4309
+ const initiallyDisabled = shouldDisableElement(element, ctx);
3957
4310
  const wrapper = document.createElement("div");
3958
4311
  wrapper.className = "mb-6 fb-field-wrapper";
3959
4312
  wrapper.setAttribute("data-field-key", element.key);
@@ -3961,6 +4314,12 @@ function renderElement2(element, ctx) {
3961
4314
  wrapper.appendChild(label);
3962
4315
  const pathKey = pathJoin(ctx.path, element.key);
3963
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);
3964
4323
  return wrapper;
3965
4324
  }
3966
4325
  setRenderElement(renderElement2);
@@ -4619,15 +4978,16 @@ var FormBuilderInstance = class {
4619
4978
  event.preventDefault();
4620
4979
  event.stopPropagation();
4621
4980
  const hintValuesJson = target.getAttribute("data-hint-values");
4981
+ const isRootHint = target.getAttribute("data-root-hint") === "true";
4622
4982
  const containerKey = target.getAttribute("data-container-key");
4623
- if (!hintValuesJson || !containerKey) {
4983
+ if (!hintValuesJson || !isRootHint && !containerKey) {
4624
4984
  console.warn("Prefill hint missing required data attributes");
4625
4985
  return;
4626
4986
  }
4627
4987
  try {
4628
4988
  const hintValues = JSON.parse(hintValuesJson);
4629
4989
  for (const fieldKey in hintValues) {
4630
- const fullPath = `${containerKey}.${fieldKey}`;
4990
+ const fullPath = isRootHint ? fieldKey : `${containerKey}.${fieldKey}`;
4631
4991
  const value = hintValues[fieldKey];
4632
4992
  this.updateField(fullPath, value);
4633
4993
  }
@@ -4635,6 +4995,27 @@ var FormBuilderInstance = class {
4635
4995
  console.error("Error parsing prefill hint values:", error);
4636
4996
  }
4637
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
+ }
4638
5019
  /**
4639
5020
  * Render form from schema
4640
5021
  */
@@ -4650,8 +5031,19 @@ var FormBuilderInstance = class {
4650
5031
  clear(root);
4651
5032
  root.setAttribute("data-fb-root", "true");
4652
5033
  injectThemeVariables(root, this.state.config.theme);
4653
- const formEl = document.createElement("div");
4654
- formEl.className = "space-y-6";
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
+ }
4655
5047
  schema.elements.forEach((element) => {
4656
5048
  if (element.hidden) {
4657
5049
  return;
@@ -4660,13 +5052,14 @@ var FormBuilderInstance = class {
4660
5052
  path: "",
4661
5053
  prefill: prefill || {},
4662
5054
  formData: prefill || {},
4663
- // Pass complete root data for displayIf evaluation
5055
+ // Pass complete root data for enableIf evaluation
4664
5056
  state: this.state,
4665
5057
  instance: this
4666
5058
  });
4667
- formEl.appendChild(block);
5059
+ fieldsWrapper.appendChild(block);
4668
5060
  });
4669
- root.appendChild(formEl);
5061
+ rootContainer.appendChild(fieldsWrapper);
5062
+ root.appendChild(rootContainer);
4670
5063
  if (!this.state.config.readonly) {
4671
5064
  root.addEventListener("click", this.handlePrefillHintClick.bind(this));
4672
5065
  }
@@ -4708,15 +5101,18 @@ var FormBuilderInstance = class {
4708
5101
  };
4709
5102
  setValidateElement(validateElement2);
4710
5103
  this.state.schema.elements.forEach((element) => {
4711
- if (element.displayIf) {
5104
+ if (element.enableIf) {
4712
5105
  try {
4713
- const shouldDisplay = evaluateDisplayCondition(element.displayIf, data);
4714
- if (!shouldDisplay) {
5106
+ const shouldEnable = evaluateEnableCondition(
5107
+ element.enableIf,
5108
+ data
5109
+ );
5110
+ if (!shouldEnable) {
4715
5111
  return;
4716
5112
  }
4717
5113
  } catch (error) {
4718
5114
  console.error(
4719
- `Error evaluating displayIf for field "${element.key}" during validation:`,
5115
+ `Error evaluating enableIf for field "${element.key}" during validation:`,
4720
5116
  error
4721
5117
  );
4722
5118
  }
@@ -4804,7 +5200,9 @@ var FormBuilderInstance = class {
4804
5200
  }
4805
5201
  if (element.type === "container" || element.type === "group") {
4806
5202
  const containerElement = element;
4807
- const nestedData = this.buildHiddenFieldsData(containerElement.elements);
5203
+ const nestedData = this.buildHiddenFieldsData(
5204
+ containerElement.elements
5205
+ );
4808
5206
  if (Object.keys(nestedData).length > 0) {
4809
5207
  if (!(key in data)) {
4810
5208
  data[key] = nestedData;
@@ -4822,7 +5220,9 @@ var FormBuilderInstance = class {
4822
5220
  */
4823
5221
  setFormData(data) {
4824
5222
  if (!this.state.schema || !this.state.formRoot) {
4825
- console.warn("setFormData: Form not initialized. Call renderForm() first.");
5223
+ console.warn(
5224
+ "setFormData: Form not initialized. Call renderForm() first."
5225
+ );
4826
5226
  return;
4827
5227
  }
4828
5228
  for (const fieldPath in data) {
@@ -4836,20 +5236,27 @@ var FormBuilderInstance = class {
4836
5236
  */
4837
5237
  updateField(fieldPath, value) {
4838
5238
  if (!this.state.schema || !this.state.formRoot) {
4839
- console.warn("updateField: Form not initialized. Call renderForm() first.");
5239
+ console.warn(
5240
+ "updateField: Form not initialized. Call renderForm() first."
5241
+ );
4840
5242
  return;
4841
5243
  }
4842
5244
  const schemaElement = this.findSchemaElement(fieldPath);
4843
5245
  if (!schemaElement) {
4844
- console.warn(`updateField: Schema element not found for path "${fieldPath}"`);
5246
+ console.warn(
5247
+ `updateField: Schema element not found for path "${fieldPath}"`
5248
+ );
4845
5249
  return;
4846
5250
  }
4847
5251
  const domElement = this.findFormElementByFieldPath(fieldPath);
4848
5252
  if (!domElement) {
4849
- console.warn(`updateField: DOM element not found for path "${fieldPath}"`);
5253
+ console.warn(
5254
+ `updateField: DOM element not found for path "${fieldPath}"`
5255
+ );
4850
5256
  return;
4851
5257
  }
4852
5258
  this.updateFieldValue(domElement, schemaElement, fieldPath, value);
5259
+ this.reevaluateConditionalFields();
4853
5260
  if (this.state.config.onChange || this.state.config.onFieldChange) {
4854
5261
  this.triggerOnChange(fieldPath, value);
4855
5262
  }
@@ -4878,7 +5285,7 @@ var FormBuilderInstance = class {
4878
5285
  }
4879
5286
  }
4880
5287
  /**
4881
- * Re-evaluate all conditional fields (displayIf) based on current form data
5288
+ * Re-evaluate all conditional fields (enableIf) based on current form data
4882
5289
  * This is called automatically when form data changes (via onChange events)
4883
5290
  */
4884
5291
  reevaluateConditionalFields() {
@@ -4887,45 +5294,80 @@ var FormBuilderInstance = class {
4887
5294
  const checkElements = (elements, currentPath) => {
4888
5295
  elements.forEach((element) => {
4889
5296
  const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
4890
- if (element.displayIf) {
4891
- const fieldWrappers = this.state.formRoot.querySelectorAll(
4892
- `[data-field-key="${element.key}"]`
4893
- );
4894
- fieldWrappers.forEach((wrapper) => {
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;
4895
5329
  try {
4896
- const shouldDisplay = evaluateDisplayCondition(
4897
- element.displayIf,
4898
- formData
4899
- // Use complete formData for condition evaluation
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
4900
5341
  );
4901
- const isCurrentlyHidden = wrapper.getAttribute("data-conditionally-hidden") === "true";
4902
- if (shouldDisplay && isCurrentlyHidden) {
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 : {};
4903
5346
  const newWrapper = renderElement2(element, {
4904
- path: fullPath,
4905
- // Use accumulated path
4906
- prefill: formData,
4907
- // Use complete formData for root-level elements
5347
+ path: currentPath,
5348
+ // Use container path (empty string for root-level)
5349
+ prefill: prefillContext,
4908
5350
  formData,
4909
- // Pass complete formData for displayIf evaluation
5351
+ // Pass complete formData for enableIf evaluation
4910
5352
  state: this.state,
4911
5353
  instance: this
4912
5354
  });
4913
5355
  wrapper.parentNode?.replaceChild(newWrapper, wrapper);
4914
- } else if (!shouldDisplay && !isCurrentlyHidden) {
4915
- const hiddenWrapper = document.createElement("div");
4916
- hiddenWrapper.className = "fb-field-wrapper-hidden";
4917
- hiddenWrapper.style.display = "none";
4918
- hiddenWrapper.setAttribute("data-field-key", element.key);
4919
- hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
4920
- wrapper.parentNode?.replaceChild(hiddenWrapper, wrapper);
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);
4921
5363
  }
4922
5364
  } catch (error) {
4923
5365
  console.error(
4924
- `Error re-evaluating displayIf for field "${element.key}":`,
5366
+ `Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
4925
5367
  error
4926
5368
  );
4927
5369
  }
4928
- });
5370
+ }
4929
5371
  }
4930
5372
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
4931
5373
  const containerData = formData?.[element.key];