@dmitryvim/form-builder 0.1.35 → 0.1.37

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.
@@ -4,6 +4,7 @@ const state = {
4
4
  schema: null,
5
5
  formRoot: null,
6
6
  resourceIndex: new Map(),
7
+ externalActions: null, // Store external actions for the current form
7
8
  version: "1.0.0",
8
9
  config: {
9
10
  // File upload configuration
@@ -129,7 +130,7 @@ function validateSchema(schema) {
129
130
  }
130
131
 
131
132
  // Form rendering
132
- function renderForm(schema, prefill) {
133
+ function renderForm(schema, prefill, actions) {
133
134
  const errors = validateSchema(schema);
134
135
  if (errors.length > 0) {
135
136
  console.error("Schema validation errors:", errors);
@@ -137,6 +138,8 @@ function renderForm(schema, prefill) {
137
138
  }
138
139
 
139
140
  state.schema = schema;
141
+ state.externalActions = actions || null;
142
+
140
143
  if (!state.formRoot) {
141
144
  console.error("No form root element set. Call setFormRoot() first.");
142
145
  return;
@@ -160,11 +163,20 @@ function renderForm(schema, prefill) {
160
163
  });
161
164
 
162
165
  state.formRoot.appendChild(formEl);
166
+
167
+ // Render external actions after form is built (only in readonly mode)
168
+ if (
169
+ state.config.readonly &&
170
+ state.externalActions &&
171
+ Array.isArray(state.externalActions)
172
+ ) {
173
+ renderExternalActions();
174
+ }
163
175
  }
164
176
 
165
177
  function renderElement(element, ctx) {
166
178
  const wrapper = document.createElement("div");
167
- wrapper.className = "mb-6";
179
+ wrapper.className = "mb-6 fb-field-wrapper";
168
180
 
169
181
  const label = document.createElement("div");
170
182
  label.className = "flex items-center mb-2";
@@ -293,7 +305,7 @@ function renderElement(element, ctx) {
293
305
  const actionBtn = document.createElement("button");
294
306
  actionBtn.type = "button";
295
307
  actionBtn.className =
296
- "px-3 py-1.5 text-sm bg-blue-50 border border-blue-200 text-blue-700 rounded-lg hover:bg-blue-100 transition-colors";
308
+ "px-3 py-2 text-sm border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors";
297
309
  actionBtn.textContent = action.label;
298
310
 
299
311
  actionBtn.addEventListener("click", (e) => {
@@ -304,7 +316,15 @@ function renderElement(element, ctx) {
304
316
  state.config.actionHandler &&
305
317
  typeof state.config.actionHandler === "function"
306
318
  ) {
307
- state.config.actionHandler(action.value);
319
+ // For schema-based actions (old system), call with just the value for backward compatibility
320
+ // Check if the handler expects 2 parameters (new system) or 1 parameter (old system)
321
+ if (state.config.actionHandler.length > 1) {
322
+ // New system: pass related_field (element key) and value
323
+ state.config.actionHandler(element.key, action.value);
324
+ } else {
325
+ // Old system: pass only value for backward compatibility
326
+ state.config.actionHandler(action.value);
327
+ }
308
328
  }
309
329
  });
310
330
 
@@ -616,11 +636,11 @@ function renderResourcePills(container, rids, onRemove) {
616
636
  slot.onclick = () => {
617
637
  // Look for file input - check parent containers that have space-y-2 class
618
638
  let filesWrapper = container.parentElement;
619
- while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
639
+ while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
620
640
  filesWrapper = filesWrapper.parentElement;
621
641
  }
622
642
  // If no parent with space-y-2, container itself might be the wrapper
623
- if (!filesWrapper && container.classList.contains('space-y-2')) {
643
+ if (!filesWrapper && container.classList.contains("space-y-2")) {
624
644
  filesWrapper = container;
625
645
  }
626
646
  const fileInput = filesWrapper?.querySelector('input[type="file"]');
@@ -641,11 +661,11 @@ function renderResourcePills(container, rids, onRemove) {
641
661
  e.stopPropagation();
642
662
  // Look for file input - check parent containers that have space-y-2 class
643
663
  let filesWrapper = container.parentElement;
644
- while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
664
+ while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
645
665
  filesWrapper = filesWrapper.parentElement;
646
666
  }
647
667
  // If no parent with space-y-2, container itself might be the wrapper
648
- if (!filesWrapper && container.classList.contains('space-y-2')) {
668
+ if (!filesWrapper && container.classList.contains("space-y-2")) {
649
669
  filesWrapper = container;
650
670
  }
651
671
  const fileInput = filesWrapper?.querySelector('input[type="file"]');
@@ -808,11 +828,11 @@ function renderResourcePills(container, rids, onRemove) {
808
828
  slot.onclick = () => {
809
829
  // Look for file input - check parent containers that have space-y-2 class
810
830
  let filesWrapper = container.parentElement;
811
- while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
831
+ while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
812
832
  filesWrapper = filesWrapper.parentElement;
813
833
  }
814
834
  // If no parent with space-y-2, container itself might be the wrapper
815
- if (!filesWrapper && container.classList.contains('space-y-2')) {
835
+ if (!filesWrapper && container.classList.contains("space-y-2")) {
816
836
  filesWrapper = container;
817
837
  }
818
838
  const fileInput = filesWrapper?.querySelector('input[type="file"]');
@@ -927,6 +947,125 @@ function addDeleteButton(container, onDelete) {
927
947
  container.appendChild(overlay);
928
948
  }
929
949
 
950
+ // JSON path resolution for external actions (currently unused but kept for future use)
951
+ // eslint-disable-next-line no-unused-vars
952
+ function resolveFieldPath(path, formData) {
953
+ // Remove leading $input_data. prefix if present
954
+ const cleanPath = path.replace(/^\$input_data\./, "");
955
+
956
+ // Split path into segments, handling array notation
957
+ const segments = cleanPath.split(/[.[\]]/).filter(Boolean);
958
+
959
+ // Try to find the corresponding form element
960
+ return findElementByPath(segments, formData);
961
+ }
962
+
963
+ function findElementByPath(segments, data, currentPath = "") {
964
+ if (segments.length === 0) return currentPath;
965
+
966
+ const [head, ...tail] = segments;
967
+
968
+ // Check if this is an array index
969
+ const isArrayIndex = /^\d+$/.test(head);
970
+
971
+ if (isArrayIndex) {
972
+ // Array index case: build path like "fieldName[index]"
973
+ const newPath = currentPath ? `${currentPath}[${head}]` : `[${head}]`;
974
+ return findElementByPath(tail, data, newPath);
975
+ } else {
976
+ // Regular field name
977
+ const newPath = currentPath ? `${currentPath}.${head}` : head;
978
+ return findElementByPath(tail, data, newPath);
979
+ }
980
+ }
981
+
982
+ function findFormElementByFieldPath(fieldPath) {
983
+ // Try to find the form element that corresponds to the field path
984
+ // This looks for elements with name attributes that match the path pattern
985
+
986
+ if (!state.formRoot) return null;
987
+
988
+ // Try exact match first
989
+ let element = state.formRoot.querySelector(`[name="${fieldPath}"]`);
990
+ if (element) return element;
991
+
992
+ // Try with array notation variations
993
+ const variations = [
994
+ fieldPath,
995
+ fieldPath.replace(/\[(\d+)\]/g, "[$1]"), // normalize array notation
996
+ fieldPath.replace(/\./g, "[") +
997
+ "]".repeat((fieldPath.match(/\./g) || []).length), // convert dots to brackets
998
+ ];
999
+
1000
+ for (const variation of variations) {
1001
+ element = state.formRoot.querySelector(`[name="${variation}"]`);
1002
+ if (element) return element;
1003
+ }
1004
+
1005
+ return null;
1006
+ }
1007
+
1008
+ function renderExternalActions() {
1009
+ if (!state.externalActions || !Array.isArray(state.externalActions)) return;
1010
+
1011
+ state.externalActions.forEach((action) => {
1012
+ if (!action.related_field || !action.value || !action.label) return;
1013
+
1014
+ // Find the form element for this related field
1015
+ const fieldElement = findFormElementByFieldPath(action.related_field);
1016
+ if (!fieldElement) {
1017
+ console.warn(
1018
+ `External action: Could not find form element for field "${action.related_field}"`,
1019
+ );
1020
+ return;
1021
+ }
1022
+
1023
+ // Find the wrapper element that contains the field using stable class
1024
+ let wrapper = fieldElement.closest(".fb-field-wrapper");
1025
+ if (!wrapper) {
1026
+ wrapper = fieldElement.parentElement;
1027
+ }
1028
+
1029
+ if (!wrapper) {
1030
+ console.warn(
1031
+ `External action: Could not find wrapper for field "${action.related_field}"`,
1032
+ );
1033
+ return;
1034
+ }
1035
+
1036
+ // Check if we already added external actions to this wrapper
1037
+ if (wrapper.querySelector(".external-actions-container")) return;
1038
+
1039
+ // Create actions container
1040
+ const actionsContainer = document.createElement("div");
1041
+ actionsContainer.className =
1042
+ "external-actions-container mt-4 flex flex-wrap gap-2";
1043
+
1044
+ // Create action button
1045
+ const actionBtn = document.createElement("button");
1046
+ actionBtn.type = "button";
1047
+ actionBtn.className =
1048
+ "px-3 py-2 text-sm border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors";
1049
+ actionBtn.textContent = action.label;
1050
+
1051
+ actionBtn.addEventListener("click", (e) => {
1052
+ e.preventDefault();
1053
+ e.stopPropagation();
1054
+
1055
+ if (
1056
+ state.config.actionHandler &&
1057
+ typeof state.config.actionHandler === "function"
1058
+ ) {
1059
+ // Call with both related_field and value for the new actions system
1060
+ state.config.actionHandler(action.related_field, action.value);
1061
+ }
1062
+ });
1063
+
1064
+ actionsContainer.appendChild(actionBtn);
1065
+ wrapper.appendChild(actionsContainer);
1066
+ });
1067
+ }
1068
+
930
1069
  function showTooltip(tooltipId, button) {
931
1070
  const tooltip = document.getElementById(tooltipId);
932
1071
  const isCurrentlyVisible = !tooltip.classList.contains("hidden");
@@ -1044,11 +1183,17 @@ function validateForm(skipValidation = false) {
1044
1183
  values.push(val);
1045
1184
 
1046
1185
  if (!skipValidation && val) {
1047
- if (element.minLength !== null && val.length < element.minLength) {
1186
+ if (
1187
+ element.minLength !== null &&
1188
+ val.length < element.minLength
1189
+ ) {
1048
1190
  errors.push(`${key}[${index}]: minLength=${element.minLength}`);
1049
1191
  markValidity(input, `minLength=${element.minLength}`);
1050
1192
  }
1051
- if (element.maxLength !== null && val.length > element.maxLength) {
1193
+ if (
1194
+ element.maxLength !== null &&
1195
+ val.length > element.maxLength
1196
+ ) {
1052
1197
  errors.push(`${key}[${index}]: maxLength=${element.maxLength}`);
1053
1198
  markValidity(input, `maxLength=${element.maxLength}`);
1054
1199
  }
@@ -1073,7 +1218,7 @@ function validateForm(skipValidation = false) {
1073
1218
  if (!skipValidation) {
1074
1219
  const minCount = element.minCount ?? 1;
1075
1220
  const maxCount = element.maxCount ?? 10;
1076
- const nonEmptyValues = values.filter(v => v.trim() !== "");
1221
+ const nonEmptyValues = values.filter((v) => v.trim() !== "");
1077
1222
 
1078
1223
  if (element.required && nonEmptyValues.length === 0) {
1079
1224
  errors.push(`${key}: required`);
@@ -1156,7 +1301,9 @@ function validateForm(skipValidation = false) {
1156
1301
  markValidity(input, `> max=${element.max}`);
1157
1302
  }
1158
1303
 
1159
- const d = Number.isInteger(element.decimals ?? 0) ? element.decimals : 0;
1304
+ const d = Number.isInteger(element.decimals ?? 0)
1305
+ ? element.decimals
1306
+ : 0;
1160
1307
  markValidity(input, null);
1161
1308
  values.push(Number(v.toFixed(d)));
1162
1309
  });
@@ -1165,7 +1312,7 @@ function validateForm(skipValidation = false) {
1165
1312
  if (!skipValidation) {
1166
1313
  const minCount = element.minCount ?? 1;
1167
1314
  const maxCount = element.maxCount ?? 10;
1168
- const nonNullValues = values.filter(v => v !== null);
1315
+ const nonNullValues = values.filter((v) => v !== null);
1169
1316
 
1170
1317
  if (element.required && nonNullValues.length === 0) {
1171
1318
  errors.push(`${key}: required`);
@@ -1229,7 +1376,7 @@ function validateForm(skipValidation = false) {
1229
1376
  if (!skipValidation) {
1230
1377
  const minCount = element.minCount ?? 1;
1231
1378
  const maxCount = element.maxCount ?? 10;
1232
- const nonEmptyValues = values.filter(v => v !== "");
1379
+ const nonEmptyValues = values.filter((v) => v !== "");
1233
1380
 
1234
1381
  if (element.required && nonEmptyValues.length === 0) {
1235
1382
  errors.push(`${key}: required`);
@@ -1261,9 +1408,11 @@ function validateForm(skipValidation = false) {
1261
1408
  // Handle file with multiple property like files type
1262
1409
  // Find the files list by locating the specific file input for this field
1263
1410
  const fullKey = pathJoin(ctx.path, key);
1264
- const pickerInput = scopeRoot.querySelector(`input[type="file"][name="${fullKey}"]`);
1265
- const filesWrapper = pickerInput?.closest('.space-y-2');
1266
- const container = filesWrapper?.querySelector('.files-list') || null;
1411
+ const pickerInput = scopeRoot.querySelector(
1412
+ `input[type="file"][name="${fullKey}"]`,
1413
+ );
1414
+ const filesWrapper = pickerInput?.closest(".space-y-2");
1415
+ const container = filesWrapper?.querySelector(".files-list") || null;
1267
1416
 
1268
1417
  const resourceIds = [];
1269
1418
  if (container) {
@@ -1351,12 +1500,18 @@ function validateForm(skipValidation = false) {
1351
1500
  const items = [];
1352
1501
  // Use full path for nested group element search
1353
1502
  const fullKey = pathJoin(ctx.path, key);
1354
- const itemElements = scopeRoot.querySelectorAll(`[name^="${fullKey}["]`);
1503
+ const itemElements = scopeRoot.querySelectorAll(
1504
+ `[name^="${fullKey}["]`,
1505
+ );
1355
1506
 
1356
1507
  // Extract actual indices from DOM element names instead of assuming sequential numbering
1357
1508
  const actualIndices = new Set();
1358
1509
  itemElements.forEach((el) => {
1359
- const match = el.name.match(new RegExp(`^${fullKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\[(\\d+)\\]`));
1510
+ const match = el.name.match(
1511
+ new RegExp(
1512
+ `^${fullKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\[(\\d+)\\]`,
1513
+ ),
1514
+ );
1360
1515
  if (match) {
1361
1516
  actualIndices.add(parseInt(match[1]));
1362
1517
  }
@@ -1374,7 +1529,8 @@ function validateForm(skipValidation = false) {
1374
1529
  element.elements.forEach((child) => {
1375
1530
  if (child.hidden) {
1376
1531
  // For hidden child elements, use their default value
1377
- itemData[child.key] = child.default !== undefined ? child.default : "";
1532
+ itemData[child.key] =
1533
+ child.default !== undefined ? child.default : "";
1378
1534
  } else {
1379
1535
  const childKey = `${fullKey}[${actualIndex}].${child.key}`;
1380
1536
  itemData[child.key] = validateElement(
@@ -1395,7 +1551,8 @@ function validateForm(skipValidation = false) {
1395
1551
  element.elements.forEach((child) => {
1396
1552
  if (child.hidden) {
1397
1553
  // For hidden child elements, use their default value
1398
- groupData[child.key] = child.default !== undefined ? child.default : "";
1554
+ groupData[child.key] =
1555
+ child.default !== undefined ? child.default : "";
1399
1556
  } else {
1400
1557
  const childKey = `${key}.${child.key}`;
1401
1558
  groupData[child.key] = validateElement(
@@ -1427,7 +1584,8 @@ function validateForm(skipValidation = false) {
1427
1584
  element.elements.forEach((child) => {
1428
1585
  if (child.hidden) {
1429
1586
  // For hidden child elements, use their default value
1430
- itemData[child.key] = child.default !== undefined ? child.default : "";
1587
+ itemData[child.key] =
1588
+ child.default !== undefined ? child.default : "";
1431
1589
  } else {
1432
1590
  const childKey = `${key}[${i}].${child.key}`;
1433
1591
  itemData[child.key] = validateElement(
@@ -1465,7 +1623,8 @@ function validateForm(skipValidation = false) {
1465
1623
  element.elements.forEach((child) => {
1466
1624
  if (child.hidden) {
1467
1625
  // For hidden child elements, use their default value
1468
- containerData[child.key] = child.default !== undefined ? child.default : "";
1626
+ containerData[child.key] =
1627
+ child.default !== undefined ? child.default : "";
1469
1628
  } else {
1470
1629
  const childKey = `${key}.${child.key}`;
1471
1630
  containerData[child.key] = validateElement(
@@ -1550,7 +1709,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1550
1709
 
1551
1710
  const textInput = document.createElement("input");
1552
1711
  textInput.type = "text";
1553
- textInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1712
+ textInput.className =
1713
+ "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1554
1714
  textInput.placeholder = element.placeholder || "Enter text";
1555
1715
  textInput.value = value;
1556
1716
  textInput.readOnly = state.config.readonly;
@@ -1571,15 +1731,16 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1571
1731
 
1572
1732
  function updateRemoveButtons() {
1573
1733
  if (state.config.readonly) return;
1574
- const items = container.querySelectorAll('.multiple-text-item');
1734
+ const items = container.querySelectorAll(".multiple-text-item");
1575
1735
  const currentCount = items.length;
1576
1736
  items.forEach((item) => {
1577
- let removeBtn = item.querySelector('.remove-item-btn');
1737
+ let removeBtn = item.querySelector(".remove-item-btn");
1578
1738
  if (!removeBtn) {
1579
- removeBtn = document.createElement('button');
1580
- removeBtn.type = 'button';
1581
- removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
1582
- removeBtn.innerHTML = '✕';
1739
+ removeBtn = document.createElement("button");
1740
+ removeBtn.type = "button";
1741
+ removeBtn.className =
1742
+ "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
1743
+ removeBtn.innerHTML = "✕";
1583
1744
  removeBtn.onclick = () => {
1584
1745
  const currentIndex = Array.from(container.children).indexOf(item);
1585
1746
  if (container.children.length > minCount) {
@@ -1594,8 +1755,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1594
1755
  }
1595
1756
  const disabled = currentCount <= minCount;
1596
1757
  removeBtn.disabled = disabled;
1597
- removeBtn.style.opacity = disabled ? '0.5' : '1';
1598
- removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
1758
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
1759
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1599
1760
  });
1600
1761
  }
1601
1762
 
@@ -1606,8 +1767,9 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1606
1767
  if (!state.config.readonly && values.length < maxCount) {
1607
1768
  const addBtn = document.createElement("button");
1608
1769
  addBtn.type = "button";
1609
- addBtn.className = "add-text-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
1610
- addBtn.textContent = `+ Add ${element.label || 'Text'}`;
1770
+ addBtn.className =
1771
+ "add-text-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
1772
+ addBtn.textContent = `+ Add ${element.label || "Text"}`;
1611
1773
  addBtn.onclick = () => {
1612
1774
  values.push(element.default || "");
1613
1775
  addTextItem(element.default || "");
@@ -1619,7 +1781,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1619
1781
  }
1620
1782
 
1621
1783
  // Render initial items
1622
- values.forEach(value => addTextItem(value));
1784
+ values.forEach((value) => addTextItem(value));
1623
1785
  updateAddButton();
1624
1786
  updateRemoveButtons();
1625
1787
 
@@ -1679,7 +1841,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1679
1841
  itemWrapper.className = "multiple-textarea-item";
1680
1842
 
1681
1843
  const textareaInput = document.createElement("textarea");
1682
- textareaInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1844
+ textareaInput.className =
1845
+ "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1683
1846
  textareaInput.placeholder = element.placeholder || "Enter text";
1684
1847
  textareaInput.rows = element.rows || 4;
1685
1848
  textareaInput.value = value;
@@ -1701,15 +1864,16 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1701
1864
 
1702
1865
  function updateRemoveButtons() {
1703
1866
  if (state.config.readonly) return;
1704
- const items = container.querySelectorAll('.multiple-textarea-item');
1867
+ const items = container.querySelectorAll(".multiple-textarea-item");
1705
1868
  const currentCount = items.length;
1706
1869
  items.forEach((item) => {
1707
- let removeBtn = item.querySelector('.remove-item-btn');
1870
+ let removeBtn = item.querySelector(".remove-item-btn");
1708
1871
  if (!removeBtn) {
1709
- removeBtn = document.createElement('button');
1710
- removeBtn.type = 'button';
1711
- removeBtn.className = 'remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm';
1712
- removeBtn.innerHTML = '✕ Remove';
1872
+ removeBtn = document.createElement("button");
1873
+ removeBtn.type = "button";
1874
+ removeBtn.className =
1875
+ "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
1876
+ removeBtn.innerHTML = "✕ Remove";
1713
1877
  removeBtn.onclick = () => {
1714
1878
  const currentIndex = Array.from(container.children).indexOf(item);
1715
1879
  if (container.children.length > minCount) {
@@ -1724,8 +1888,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1724
1888
  }
1725
1889
  const disabled = currentCount <= minCount;
1726
1890
  removeBtn.disabled = disabled;
1727
- removeBtn.style.opacity = disabled ? '0.5' : '1';
1728
- removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
1891
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
1892
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1729
1893
  });
1730
1894
  }
1731
1895
 
@@ -1736,8 +1900,9 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1736
1900
  if (!state.config.readonly && values.length < maxCount) {
1737
1901
  const addBtn = document.createElement("button");
1738
1902
  addBtn.type = "button";
1739
- 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";
1740
- addBtn.textContent = `+ Add ${element.label || 'Textarea'}`;
1903
+ addBtn.className =
1904
+ "add-textarea-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
1905
+ addBtn.textContent = `+ Add ${element.label || "Textarea"}`;
1741
1906
  addBtn.onclick = () => {
1742
1907
  values.push(element.default || "");
1743
1908
  addTextareaItem(element.default || "");
@@ -1749,7 +1914,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1749
1914
  }
1750
1915
 
1751
1916
  // Render initial items
1752
- values.forEach(value => addTextareaItem(value));
1917
+ values.forEach((value) => addTextareaItem(value));
1753
1918
  updateAddButton();
1754
1919
  updateRemoveButtons();
1755
1920
 
@@ -1813,7 +1978,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1813
1978
 
1814
1979
  const numberInput = document.createElement("input");
1815
1980
  numberInput.type = "number";
1816
- numberInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1981
+ numberInput.className =
1982
+ "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1817
1983
  numberInput.placeholder = element.placeholder || "0";
1818
1984
  if (element.min !== undefined) numberInput.min = element.min;
1819
1985
  if (element.max !== undefined) numberInput.max = element.max;
@@ -1837,15 +2003,16 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1837
2003
 
1838
2004
  function updateRemoveButtons() {
1839
2005
  if (state.config.readonly) return;
1840
- const items = container.querySelectorAll('.multiple-number-item');
2006
+ const items = container.querySelectorAll(".multiple-number-item");
1841
2007
  const currentCount = items.length;
1842
2008
  items.forEach((item) => {
1843
- let removeBtn = item.querySelector('.remove-item-btn');
2009
+ let removeBtn = item.querySelector(".remove-item-btn");
1844
2010
  if (!removeBtn) {
1845
- removeBtn = document.createElement('button');
1846
- removeBtn.type = 'button';
1847
- removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
1848
- removeBtn.innerHTML = '✕';
2011
+ removeBtn = document.createElement("button");
2012
+ removeBtn.type = "button";
2013
+ removeBtn.className =
2014
+ "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
2015
+ removeBtn.innerHTML = "✕";
1849
2016
  removeBtn.onclick = () => {
1850
2017
  const currentIndex = Array.from(container.children).indexOf(item);
1851
2018
  if (container.children.length > minCount) {
@@ -1860,8 +2027,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1860
2027
  }
1861
2028
  const disabled = currentCount <= minCount;
1862
2029
  removeBtn.disabled = disabled;
1863
- removeBtn.style.opacity = disabled ? '0.5' : '1';
1864
- removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
2030
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
2031
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1865
2032
  });
1866
2033
  }
1867
2034
 
@@ -1872,8 +2039,9 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1872
2039
  if (!state.config.readonly && values.length < maxCount) {
1873
2040
  const addBtn = document.createElement("button");
1874
2041
  addBtn.type = "button";
1875
- 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";
1876
- addBtn.textContent = `+ Add ${element.label || 'Number'}`;
2042
+ addBtn.className =
2043
+ "add-number-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
2044
+ addBtn.textContent = `+ Add ${element.label || "Number"}`;
1877
2045
  addBtn.onclick = () => {
1878
2046
  values.push(element.default || "");
1879
2047
  addNumberItem(element.default || "");
@@ -1885,7 +2053,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1885
2053
  }
1886
2054
 
1887
2055
  // Render initial items
1888
- values.forEach(value => addNumberItem(value));
2056
+ values.forEach((value) => addNumberItem(value));
1889
2057
  updateAddButton();
1890
2058
  updateRemoveButtons();
1891
2059
 
@@ -1931,7 +2099,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1931
2099
  const maxCount = element.maxCount ?? 10;
1932
2100
 
1933
2101
  while (values.length < minCount) {
1934
- values.push(element.default || (element.options?.[0]?.value || ""));
2102
+ values.push(element.default || element.options?.[0]?.value || "");
1935
2103
  }
1936
2104
 
1937
2105
  const container = document.createElement("div");
@@ -1953,7 +2121,8 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1953
2121
  itemWrapper.className = "multiple-select-item flex items-center gap-2";
1954
2122
 
1955
2123
  const selectInput = document.createElement("select");
1956
- selectInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
2124
+ selectInput.className =
2125
+ "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1957
2126
  selectInput.disabled = state.config.readonly;
1958
2127
 
1959
2128
  // Add options
@@ -1983,15 +2152,16 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1983
2152
 
1984
2153
  function updateRemoveButtons() {
1985
2154
  if (state.config.readonly) return;
1986
- const items = container.querySelectorAll('.multiple-select-item');
2155
+ const items = container.querySelectorAll(".multiple-select-item");
1987
2156
  const currentCount = items.length;
1988
2157
  items.forEach((item) => {
1989
- let removeBtn = item.querySelector('.remove-item-btn');
2158
+ let removeBtn = item.querySelector(".remove-item-btn");
1990
2159
  if (!removeBtn) {
1991
- removeBtn = document.createElement('button');
1992
- removeBtn.type = 'button';
1993
- removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
1994
- removeBtn.innerHTML = '✕';
2160
+ removeBtn = document.createElement("button");
2161
+ removeBtn.type = "button";
2162
+ removeBtn.className =
2163
+ "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
2164
+ removeBtn.innerHTML = "✕";
1995
2165
  removeBtn.onclick = () => {
1996
2166
  const currentIndex = Array.from(container.children).indexOf(item);
1997
2167
  if (container.children.length > minCount) {
@@ -2006,8 +2176,8 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2006
2176
  }
2007
2177
  const disabled = currentCount <= minCount;
2008
2178
  removeBtn.disabled = disabled;
2009
- removeBtn.style.opacity = disabled ? '0.5' : '1';
2010
- removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
2179
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
2180
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
2011
2181
  });
2012
2182
  }
2013
2183
 
@@ -2018,10 +2188,12 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2018
2188
  if (!state.config.readonly && values.length < maxCount) {
2019
2189
  const addBtn = document.createElement("button");
2020
2190
  addBtn.type = "button";
2021
- 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";
2022
- addBtn.textContent = `+ Add ${element.label || 'Selection'}`;
2191
+ addBtn.className =
2192
+ "add-select-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
2193
+ addBtn.textContent = `+ Add ${element.label || "Selection"}`;
2023
2194
  addBtn.onclick = () => {
2024
- const defaultValue = element.default || (element.options?.[0]?.value || "");
2195
+ const defaultValue =
2196
+ element.default || element.options?.[0]?.value || "";
2025
2197
  values.push(defaultValue);
2026
2198
  addSelectItem(defaultValue);
2027
2199
  updateAddButton();
@@ -2032,7 +2204,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2032
2204
  }
2033
2205
 
2034
2206
  // Render initial items
2035
- values.forEach(value => addSelectItem(value));
2207
+ values.forEach((value) => addSelectItem(value));
2036
2208
  updateAddButton();
2037
2209
  updateRemoveButtons();
2038
2210
 
@@ -2321,17 +2493,18 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
2321
2493
  // Show count and min/max info
2322
2494
  const countInfo = document.createElement("div");
2323
2495
  countInfo.className = "text-xs text-gray-500 mt-2";
2324
- const countText = `${initialFiles.length} file${initialFiles.length !== 1 ? 's' : ''}`;
2325
- const minMaxText = minFiles > 0 || maxFiles < Infinity
2326
- ? ` (${minFiles}-${maxFiles} allowed)`
2327
- : '';
2496
+ const countText = `${initialFiles.length} file${initialFiles.length !== 1 ? "s" : ""}`;
2497
+ const minMaxText =
2498
+ minFiles > 0 || maxFiles < Infinity
2499
+ ? ` (${minFiles}-${maxFiles} allowed)`
2500
+ : "";
2328
2501
  countInfo.textContent = countText + minMaxText;
2329
2502
 
2330
2503
  // Remove previous count info
2331
- const existingCount = filesWrapper.querySelector('.file-count-info');
2504
+ const existingCount = filesWrapper.querySelector(".file-count-info");
2332
2505
  if (existingCount) existingCount.remove();
2333
2506
 
2334
- countInfo.className += ' file-count-info';
2507
+ countInfo.className += " file-count-info";
2335
2508
  filesWrapper.appendChild(countInfo);
2336
2509
  };
2337
2510
 
@@ -2639,7 +2812,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2639
2812
  prefill: {},
2640
2813
  };
2641
2814
  const item = document.createElement("div");
2642
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2815
+ item.className =
2816
+ "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2643
2817
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
2644
2818
 
2645
2819
  element.elements.forEach((child) => {
@@ -2678,7 +2852,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2678
2852
  addBtn.disabled = currentCount >= max;
2679
2853
  addBtn.style.opacity = currentCount >= max ? "0.5" : "1";
2680
2854
  }
2681
- left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? '' : max})</span>`;
2855
+ left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? "" : max})</span>`;
2682
2856
  };
2683
2857
 
2684
2858
  if (!state.config.readonly) {
@@ -2693,7 +2867,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2693
2867
  prefill: prefillObj || {},
2694
2868
  };
2695
2869
  const item = document.createElement("div");
2696
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2870
+ item.className =
2871
+ "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2697
2872
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
2698
2873
 
2699
2874
  element.elements.forEach((child) => {
@@ -2731,7 +2906,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2731
2906
  prefill: {},
2732
2907
  };
2733
2908
  const item = document.createElement("div");
2734
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2909
+ item.className =
2910
+ "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2735
2911
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
2736
2912
 
2737
2913
  element.elements.forEach((child) => {