@dmitryvim/form-builder 0.5.2 → 0.5.4

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.
@@ -19,6 +19,69 @@ function t(key, state, params) {
19
19
  return text;
20
20
  }
21
21
 
22
+ // src/utils/helpers.ts
23
+ function isElementReadonly(element, state, ctx) {
24
+ return element.readonly === true || state.config.readonly === true || (ctx == null ? void 0 : ctx.inheritedReadonly) === true;
25
+ }
26
+ function isPlainObject(obj) {
27
+ return obj && typeof obj === "object" && obj.constructor === Object;
28
+ }
29
+ function escapeHtml(text) {
30
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
31
+ }
32
+ function getElementLookupKey(element, state) {
33
+ if (element.key) {
34
+ return element.key;
35
+ }
36
+ const cached = state.syntheticElementIds.get(element);
37
+ if (cached !== void 0) {
38
+ return cached;
39
+ }
40
+ const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
41
+ state.syntheticElementIds.set(element, id);
42
+ return id;
43
+ }
44
+ function pathJoin(base, key) {
45
+ return base ? `${base}.${key}` : key;
46
+ }
47
+ function findDirectContainerRows(scopeRoot, containerPath) {
48
+ if (!containerPath) return [];
49
+ const all = scopeRoot.querySelectorAll("[data-container-item]");
50
+ return Array.from(all).filter((el) => {
51
+ const attr = el.getAttribute("data-container-item") || "";
52
+ if (!attr.startsWith(`${containerPath}[`)) return false;
53
+ return /^\[\d+\]$/.test(attr.slice(containerPath.length));
54
+ });
55
+ }
56
+ function clear(node) {
57
+ while (node.firstChild) node.removeChild(node.firstChild);
58
+ }
59
+ function formatFileSize(bytes) {
60
+ if (bytes < 1024) return `${bytes} B`;
61
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
62
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
63
+ }
64
+ function serializeHiddenValue(value) {
65
+ if (value === null || value === void 0) return "";
66
+ return JSON.stringify(value);
67
+ }
68
+ function deserializeHiddenValue(raw) {
69
+ if (raw === "") return null;
70
+ try {
71
+ return JSON.parse(raw);
72
+ } catch {
73
+ return raw;
74
+ }
75
+ }
76
+ function createHiddenInput(name, value) {
77
+ const input = document.createElement("input");
78
+ input.type = "hidden";
79
+ input.name = name;
80
+ input.setAttribute("data-hidden-field", "true");
81
+ input.value = serializeHiddenValue(value);
82
+ return input;
83
+ }
84
+
22
85
  // src/utils/validation.ts
23
86
  function addLengthHint(element, parts, state) {
24
87
  if (element.minLength != null || element.maxLength != null) {
@@ -191,6 +254,65 @@ function validateSchema(schema) {
191
254
  }
192
255
  }
193
256
  }
257
+ function validateCountBounds(element, elementPath, errors2) {
258
+ var _a, _b;
259
+ const el = element;
260
+ if (el.type === "group") {
261
+ if (!isPlainObject(el.repeat)) return;
262
+ checkBounds(
263
+ elementPath,
264
+ (_a = el.repeat) == null ? void 0 : _a.min,
265
+ (_b = el.repeat) == null ? void 0 : _b.max,
266
+ "repeat.min",
267
+ "repeat.max",
268
+ el.required === true,
269
+ errors2
270
+ );
271
+ return;
272
+ }
273
+ const isMultiple = el.multiple === true || el.type === "files";
274
+ if (!isMultiple) return;
275
+ checkBounds(
276
+ elementPath,
277
+ el.minCount,
278
+ el.maxCount,
279
+ "minCount",
280
+ "maxCount",
281
+ el.required === true,
282
+ errors2
283
+ );
284
+ }
285
+ function checkBounds(elementPath, minCount, maxCount, minName, maxName, requiredImpliesFloor, errors2) {
286
+ for (const [name, bound] of [
287
+ [minName, minCount],
288
+ [maxName, maxCount]
289
+ ]) {
290
+ if (bound !== void 0 && typeof bound !== "number") {
291
+ errors2.push(
292
+ `${elementPath}: ${name} must be a number (got ${typeof bound})`
293
+ );
294
+ }
295
+ }
296
+ const min = typeof minCount === "number" ? minCount : void 0;
297
+ const max = typeof maxCount === "number" ? maxCount : void 0;
298
+ if (max !== void 0 && (max < 0 || Number.isNaN(max))) {
299
+ errors2.push(
300
+ `${elementPath}: ${maxName} must be a non-negative number or Infinity (got ${max})`
301
+ );
302
+ }
303
+ if (min !== void 0 && (min < 0 || !Number.isFinite(min))) {
304
+ errors2.push(
305
+ `${elementPath}: ${minName} must be a finite non-negative number (got ${min})`
306
+ );
307
+ }
308
+ const effectiveMin = min != null ? min : requiredImpliesFloor ? 1 : void 0;
309
+ if (effectiveMin !== void 0 && max !== void 0 && effectiveMin > max) {
310
+ const shown = min !== void 0 ? `${minName} (${min})` : `required: true (implies ${minName} 1)`;
311
+ errors2.push(
312
+ `${elementPath}: ${shown} cannot be greater than ${maxName} (${max})`
313
+ );
314
+ }
315
+ }
194
316
  function validateElements(elements, path) {
195
317
  elements.forEach((element, index) => {
196
318
  const elementPath = `${path}[${index}]`;
@@ -200,6 +322,15 @@ function validateSchema(schema) {
200
322
  if (!element.key && element.type !== "markdown") {
201
323
  errors.push(`${elementPath}: missing key`);
202
324
  }
325
+ validateCountBounds(element, elementPath, errors);
326
+ if (element.type === "number" && "decimals" in element) {
327
+ const decimals = element.decimals;
328
+ if (decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0)) {
329
+ errors.push(
330
+ `${elementPath}: decimals must be a non-negative integer (got ${JSON.stringify(decimals)})`
331
+ );
332
+ }
333
+ }
203
334
  if (element.type === "markdown") {
204
335
  const content = element.content;
205
336
  if (typeof content !== "string") {
@@ -280,71 +411,6 @@ function validateSchema(schema) {
280
411
  return errors;
281
412
  }
282
413
 
283
- // src/utils/helpers.ts
284
- function isElementReadonly(element, state, ctx) {
285
- return element.readonly === true || state.config.readonly === true || (ctx == null ? void 0 : ctx.inheritedReadonly) === true;
286
- }
287
- function isPlainObject(obj) {
288
- return obj && typeof obj === "object" && obj.constructor === Object;
289
- }
290
- function escapeHtml(text) {
291
- const div = document.createElement("div");
292
- div.textContent = text;
293
- return div.innerHTML;
294
- }
295
- function getElementLookupKey(element, state) {
296
- if (element.key) {
297
- return element.key;
298
- }
299
- const cached = state.syntheticElementIds.get(element);
300
- if (cached !== void 0) {
301
- return cached;
302
- }
303
- const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
304
- state.syntheticElementIds.set(element, id);
305
- return id;
306
- }
307
- function pathJoin(base, key) {
308
- return base ? `${base}.${key}` : key;
309
- }
310
- function findDirectContainerRows(scopeRoot, containerPath) {
311
- if (!containerPath) return [];
312
- const all = scopeRoot.querySelectorAll("[data-container-item]");
313
- return Array.from(all).filter((el) => {
314
- const attr = el.getAttribute("data-container-item") || "";
315
- if (!attr.startsWith(`${containerPath}[`)) return false;
316
- return /^\[\d+\]$/.test(attr.slice(containerPath.length));
317
- });
318
- }
319
- function clear(node) {
320
- while (node.firstChild) node.removeChild(node.firstChild);
321
- }
322
- function formatFileSize(bytes) {
323
- if (bytes < 1024) return `${bytes} B`;
324
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
325
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
326
- }
327
- function serializeHiddenValue(value) {
328
- if (value === null || value === void 0) return "";
329
- return typeof value === "object" ? JSON.stringify(value) : String(value);
330
- }
331
- function deserializeHiddenValue(raw) {
332
- if (raw === "") return null;
333
- try {
334
- return JSON.parse(raw);
335
- } catch {
336
- return raw;
337
- }
338
- }
339
- function createHiddenInput(name, value) {
340
- const input = document.createElement("input");
341
- input.type = "hidden";
342
- input.name = name;
343
- input.setAttribute("data-hidden-field", "true");
344
- input.value = serializeHiddenValue(value);
345
- return input;
346
- }
347
-
348
414
  // src/utils/enable-conditions.ts
349
415
  function getValueByPath(data, path) {
350
416
  if (!data || typeof data !== "object") {
@@ -898,7 +964,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
898
964
  overflow-wrap: anywhere;
899
965
  `;
900
966
  textInput.name = pathKey;
901
- textInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
967
+ textInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
902
968
  textInput.value = ctx.prefill[element.key] || element.default || "";
903
969
  textInput.readOnly = readonly;
904
970
  applySingleLineMode(textInput);
@@ -998,6 +1064,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
998
1064
  rem.setAttribute("aria-label", t("removeElement", state));
999
1065
  rem.innerHTML = BIN_ICON_SVG;
1000
1066
  rem.onclick = () => {
1067
+ var _a2;
1001
1068
  const chips = list.querySelectorAll(".fb-chip");
1002
1069
  const idx = Array.prototype.indexOf.call(chips, chip);
1003
1070
  if (idx < 0) return;
@@ -1011,6 +1078,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1011
1078
  updateIndices();
1012
1079
  updateAddButton();
1013
1080
  updateRemoveButtons();
1081
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1014
1082
  };
1015
1083
  chip.appendChild(rem);
1016
1084
  }
@@ -1031,10 +1099,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1031
1099
  const handle = createAddItemRow(
1032
1100
  "text",
1033
1101
  () => {
1102
+ var _a2;
1034
1103
  values.push(element.default || "");
1035
1104
  addChip(element.default || "");
1036
1105
  updateAddButton();
1037
1106
  updateRemoveButtons();
1107
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1038
1108
  },
1039
1109
  { label: element.addLabel }
1040
1110
  );
@@ -1151,7 +1221,7 @@ function validateTextElement(element, key, context) {
1151
1221
  }
1152
1222
  return { value: values, errors };
1153
1223
  } else {
1154
- const input = scopeRoot.querySelector(`[name$="${key}"]`);
1224
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
1155
1225
  const val = (_c = input == null ? void 0 : input.value) != null ? _c : "";
1156
1226
  if (!skipValidation && element.required && val === "") {
1157
1227
  const msg = t("required", context.state);
@@ -1315,6 +1385,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1315
1385
  removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
1316
1386
  removeBtn.innerHTML = "\u2715";
1317
1387
  removeBtn.onclick = () => {
1388
+ var _a2;
1318
1389
  const currentIndex = Array.from(container.children).indexOf(
1319
1390
  item
1320
1391
  );
@@ -1324,6 +1395,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1324
1395
  updateIndices();
1325
1396
  updateAddButton();
1326
1397
  updateRemoveButtons();
1398
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1327
1399
  }
1328
1400
  };
1329
1401
  item.appendChild(removeBtn);
@@ -1339,10 +1411,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1339
1411
  const handle = createAddItemRow(
1340
1412
  "textarea",
1341
1413
  () => {
1414
+ var _a2;
1342
1415
  values.push(element.default || "");
1343
1416
  addTextareaItem(element.default || "");
1344
1417
  updateAddButton();
1345
1418
  updateRemoveButtons();
1419
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1346
1420
  },
1347
1421
  { label: element.addLabel }
1348
1422
  );
@@ -1628,6 +1702,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1628
1702
  removeBtn.className = "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
1629
1703
  removeBtn.innerHTML = "\u2715";
1630
1704
  removeBtn.onclick = () => {
1705
+ var _a2;
1631
1706
  const currentIndex = Array.from(container.children).indexOf(
1632
1707
  item
1633
1708
  );
@@ -1637,6 +1712,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1637
1712
  updateIndices();
1638
1713
  updateAddButton();
1639
1714
  updateRemoveButtons();
1715
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1640
1716
  }
1641
1717
  };
1642
1718
  item.appendChild(removeBtn);
@@ -1652,10 +1728,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1652
1728
  const handle = createAddItemRow(
1653
1729
  "number",
1654
1730
  () => {
1731
+ var _a2;
1655
1732
  values.push(element.default || "");
1656
1733
  addNumberItem(element.default || "");
1657
1734
  updateAddButton();
1658
1735
  updateRemoveButtons();
1736
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1659
1737
  },
1660
1738
  { label: element.addLabel }
1661
1739
  );
@@ -1671,7 +1749,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1671
1749
  updateRemoveButtons();
1672
1750
  }
1673
1751
  function validateNumberElement(element, key, context) {
1674
- var _a, _b, _c, _d, _e;
1752
+ var _a, _b, _c;
1675
1753
  const errors = [];
1676
1754
  const { scopeRoot, skipValidation } = context;
1677
1755
  const markValidity = (input, errorMessage) => {
@@ -1731,7 +1809,7 @@ function validateNumberElement(element, key, context) {
1731
1809
  );
1732
1810
  const values = [];
1733
1811
  inputs.forEach((input, index) => {
1734
- var _a2, _b2, _c2;
1812
+ var _a2;
1735
1813
  const raw = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
1736
1814
  if (raw === "") {
1737
1815
  values.push(null);
@@ -1747,8 +1825,7 @@ function validateNumberElement(element, key, context) {
1747
1825
  return;
1748
1826
  }
1749
1827
  validateNumberInput(input, v, `${key}[${index}]`);
1750
- const d = Number.isInteger((_b2 = element.decimals) != null ? _b2 : 0) ? (_c2 = element.decimals) != null ? _c2 : 0 : 0;
1751
- values.push(Number(v.toFixed(d)));
1828
+ values.push(applyDecimals(v, element.decimals));
1752
1829
  });
1753
1830
  if (!skipValidation) {
1754
1831
  const { state } = context;
@@ -1767,7 +1844,7 @@ function validateNumberElement(element, key, context) {
1767
1844
  }
1768
1845
  return { value: values, errors };
1769
1846
  } else {
1770
- const input = scopeRoot.querySelector(`[name$="${key}"]`);
1847
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
1771
1848
  const raw = (_c = input == null ? void 0 : input.value) != null ? _c : "";
1772
1849
  const { state } = context;
1773
1850
  if (!skipValidation && element.required && raw === "") {
@@ -1788,10 +1865,13 @@ function validateNumberElement(element, key, context) {
1788
1865
  return { value: null, errors };
1789
1866
  }
1790
1867
  validateNumberInput(input, v, key);
1791
- const d = Number.isInteger((_d = element.decimals) != null ? _d : 0) ? (_e = element.decimals) != null ? _e : 0 : 0;
1792
- return { value: Number(v.toFixed(d)), errors };
1868
+ return { value: applyDecimals(v, element.decimals), errors };
1793
1869
  }
1794
1870
  }
1871
+ function applyDecimals(v, decimals) {
1872
+ if (!Number.isInteger(decimals) || decimals < 0) return v;
1873
+ return Number(v.toFixed(decimals));
1874
+ }
1795
1875
  function updateNumberField(element, fieldPath, value, context) {
1796
1876
  const { scopeRoot } = context;
1797
1877
  if (element.multiple) {
@@ -1938,6 +2018,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1938
2018
  removeBtn.className = "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
1939
2019
  removeBtn.innerHTML = "\u2715";
1940
2020
  removeBtn.onclick = () => {
2021
+ var _a2;
1941
2022
  const currentIndex = Array.from(container.children).indexOf(item);
1942
2023
  if (container.children.length > minCount) {
1943
2024
  values.splice(currentIndex, 1);
@@ -1945,6 +2026,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1945
2026
  updateIndices();
1946
2027
  updateAddButton();
1947
2028
  updateRemoveButtons();
2029
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1948
2030
  }
1949
2031
  };
1950
2032
  item.appendChild(removeBtn);
@@ -1960,12 +2042,13 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1960
2042
  const handle = createAddItemRow(
1961
2043
  "select",
1962
2044
  () => {
1963
- var _a2, _b2;
2045
+ var _a2, _b2, _c2;
1964
2046
  const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
1965
2047
  values.push(defaultValue);
1966
2048
  addSelectItem(defaultValue);
1967
2049
  updateAddButton();
1968
2050
  updateRemoveButtons();
2051
+ (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
1969
2052
  },
1970
2053
  { label: element.addLabel }
1971
2054
  );
@@ -2055,7 +2138,7 @@ function validateSelectElement(element, key, context) {
2055
2138
  return { value: values, errors };
2056
2139
  } else {
2057
2140
  const input = scopeRoot.querySelector(
2058
- `[name$="${key}"]`
2141
+ `[name="${key}"]`
2059
2142
  );
2060
2143
  const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
2061
2144
  if (!skipValidation && element.required && val === "") {
@@ -2380,6 +2463,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2380
2463
  removeBtn.style.backgroundColor = "transparent";
2381
2464
  });
2382
2465
  removeBtn.onclick = () => {
2466
+ var _a2;
2383
2467
  const currentIndex = Array.from(container.children).indexOf(
2384
2468
  item
2385
2469
  );
@@ -2389,6 +2473,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2389
2473
  updateIndices();
2390
2474
  updateAddButton();
2391
2475
  updateRemoveButtons();
2476
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
2392
2477
  }
2393
2478
  };
2394
2479
  item.appendChild(removeBtn);
@@ -2404,12 +2489,13 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2404
2489
  const handle = createAddItemRow(
2405
2490
  "switcher",
2406
2491
  () => {
2407
- var _a2, _b2;
2492
+ var _a2, _b2, _c2;
2408
2493
  const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2409
2494
  values.push(defaultValue);
2410
2495
  addSwitcherItem(defaultValue);
2411
2496
  updateAddButton();
2412
2497
  updateRemoveButtons();
2498
+ (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
2413
2499
  },
2414
2500
  { label: element.addLabel }
2415
2501
  );
@@ -2508,7 +2594,7 @@ function validateSwitcherElement(element, key, context) {
2508
2594
  return { value: values, errors };
2509
2595
  } else {
2510
2596
  const input = scopeRoot.querySelector(
2511
- `input[type="hidden"][name$="${key}"]`
2597
+ `input[type="hidden"][name="${key}"]`
2512
2598
  );
2513
2599
  const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
2514
2600
  if (!skipValidation && element.required && val === "") {
@@ -5604,7 +5690,7 @@ function validateSingleFile(element, key, context) {
5604
5690
  const { scopeRoot, skipValidation, state } = context;
5605
5691
  const errors = [];
5606
5692
  const input = scopeRoot.querySelector(
5607
- `input[name$="${key}"][type="hidden"]`
5693
+ `input[name="${key}"][type="hidden"]`
5608
5694
  );
5609
5695
  const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
5610
5696
  if (!skipValidation && element.required && rid === "") {
@@ -6028,6 +6114,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6028
6114
  removeBtn.style.backgroundColor = "transparent";
6029
6115
  });
6030
6116
  removeBtn.onclick = () => {
6117
+ var _a2;
6031
6118
  const currentIndex = Array.from(container.children).indexOf(
6032
6119
  item
6033
6120
  );
@@ -6037,6 +6124,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6037
6124
  updateIndices();
6038
6125
  updateAddButton();
6039
6126
  updateRemoveButtons();
6127
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6040
6128
  }
6041
6129
  };
6042
6130
  item.appendChild(removeBtn);
@@ -6052,11 +6140,13 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6052
6140
  const handle = createAddItemRow(
6053
6141
  "colour",
6054
6142
  () => {
6143
+ var _a2;
6055
6144
  const defaultColour = element.default || "#000000";
6056
6145
  values.push(defaultColour);
6057
6146
  addColourItem(defaultColour);
6058
6147
  updateAddButton();
6059
6148
  updateRemoveButtons();
6149
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6060
6150
  },
6061
6151
  { label: element.addLabel }
6062
6152
  );
@@ -6490,6 +6580,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6490
6580
  removeBtn.style.backgroundColor = "transparent";
6491
6581
  });
6492
6582
  removeBtn.onclick = () => {
6583
+ var _a2;
6493
6584
  const currentIndex = Array.from(container.children).indexOf(
6494
6585
  item
6495
6586
  );
@@ -6499,6 +6590,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6499
6590
  updateIndices();
6500
6591
  updateAddButton();
6501
6592
  updateRemoveButtons();
6593
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6502
6594
  }
6503
6595
  };
6504
6596
  item.appendChild(removeBtn);
@@ -6514,10 +6606,12 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6514
6606
  const handle = createAddItemRow(
6515
6607
  "slider",
6516
6608
  () => {
6609
+ var _a2;
6517
6610
  values.push(defaultValue);
6518
6611
  addSliderItem(defaultValue);
6519
6612
  updateAddButton();
6520
6613
  updateRemoveButtons();
6614
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6521
6615
  },
6522
6616
  { label: element.addLabel }
6523
6617
  );
@@ -6788,6 +6882,8 @@ function extractRootFormData(formRoot) {
6788
6882
  if (input.checked) {
6789
6883
  data[fieldName] = input.value;
6790
6884
  }
6885
+ } else if (input.dataset.hiddenField) {
6886
+ data[fieldName] = deserializeHiddenValue(input.value);
6791
6887
  } else {
6792
6888
  data[fieldName] = input.value;
6793
6889
  }
@@ -6878,11 +6974,14 @@ function getChildWrapperClass(columns) {
6878
6974
  const cols = columns || 1;
6879
6975
  return cols === 1 ? "fb-row" : `grid grid-cols-${cols} gap-2`;
6880
6976
  }
6881
- function mountRemoveButton(item, onRemove, state) {
6977
+ function mountRemoveButton(item, onRemove, state, containerLabel) {
6882
6978
  const rem = document.createElement("button");
6883
6979
  rem.type = "button";
6884
6980
  rem.className = "fb-item-remove";
6885
- rem.setAttribute("aria-label", t("removeElement", state));
6981
+ rem.setAttribute(
6982
+ "aria-label",
6983
+ containerLabel ? t("removeRowFrom", state, { label: containerLabel }) : t("removeRow", state)
6984
+ );
6886
6985
  rem.style.cssText = `
6887
6986
  width: 24px;
6888
6987
  height: 24px;
@@ -6905,7 +7004,7 @@ function mountRemoveButton(item, onRemove, state) {
6905
7004
  item.classList.add("fb-row-removable");
6906
7005
  item.insertBefore(rem, item.firstChild);
6907
7006
  }
6908
- function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7007
+ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
6909
7008
  var _a, _b, _c, _d;
6910
7009
  const state = ctx.state;
6911
7010
  const containerIsReadonly = isElementReadonly(element, state, ctx);
@@ -6936,9 +7035,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6936
7035
  );
6937
7036
  const countItems = () => directRows().length;
6938
7037
  const handleRemoveItem = (item) => {
7038
+ var _a2;
6939
7039
  if (countItems() <= min) return;
6940
7040
  item.remove();
6941
7041
  updateAddButton();
7042
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6942
7043
  };
6943
7044
  const createContainerItem = (idx, rowPrefill, formData) => {
6944
7045
  const subCtx = {
@@ -6972,11 +7073,17 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6972
7073
  });
6973
7074
  item.appendChild(childWrapper);
6974
7075
  if (!containerIsReadonly) {
6975
- mountRemoveButton(item, () => handleRemoveItem(item), state);
7076
+ mountRemoveButton(
7077
+ item,
7078
+ () => handleRemoveItem(item),
7079
+ state,
7080
+ element.label
7081
+ );
6976
7082
  }
6977
7083
  return item;
6978
7084
  };
6979
7085
  const handleAddItem = () => {
7086
+ var _a2;
6980
7087
  if (countItems() >= max) return;
6981
7088
  const item = createContainerItem(
6982
7089
  takeRowIndex(),
@@ -6991,6 +7098,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6991
7098
  itemsWrap.appendChild(item);
6992
7099
  }
6993
7100
  updateAddButton();
7101
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6994
7102
  };
6995
7103
  let slideAddTile = null;
6996
7104
  let slideAddUpdate = null;
@@ -7138,7 +7246,7 @@ function validateContainerElement(element, key, context) {
7138
7246
  );
7139
7247
  if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
7140
7248
  Object.assign(itemData, childResult.value);
7141
- } else {
7249
+ } else if (!childResult.skip && child.key) {
7142
7250
  itemData[child.key] = childResult.value;
7143
7251
  }
7144
7252
  });
@@ -7180,7 +7288,7 @@ function validateContainerElement(element, key, context) {
7180
7288
  );
7181
7289
  if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
7182
7290
  Object.assign(containerData, childResult.value);
7183
- } else {
7291
+ } else if (!childResult.skip && child.key) {
7184
7292
  containerData[child.key] = childResult.value;
7185
7293
  }
7186
7294
  }
@@ -7200,15 +7308,18 @@ function updateContainerField(element, fieldPath, value, context) {
7200
7308
  );
7201
7309
  return;
7202
7310
  }
7311
+ const rows = findDirectContainerRows(scopeRoot, fieldPath);
7203
7312
  value.forEach((itemValue, index) => {
7204
- if (isPlainObject(itemValue)) {
7313
+ var _a;
7314
+ const rowPath = (_a = rows[index]) == null ? void 0 : _a.getAttribute("data-container-item");
7315
+ if (isPlainObject(itemValue) && rowPath) {
7205
7316
  element.elements.forEach((childElement) => {
7206
- var _a, _b;
7317
+ var _a2, _b;
7207
7318
  if (childElement.type === "markdown" || !childElement.key) return;
7208
- const childPath = `${fieldPath}[${index}].${childElement.key}`;
7319
+ const childPath = `${rowPath}.${childElement.key}`;
7209
7320
  if (childElement.type === "richinput" && childElement.flatOutput) {
7210
7321
  const richChild = childElement;
7211
- const textKey = (_a = richChild.textKey) != null ? _a : "text";
7322
+ const textKey = (_a2 = richChild.textKey) != null ? _a2 : "text";
7212
7323
  const filesKey = (_b = richChild.filesKey) != null ? _b : "files";
7213
7324
  const containerValue = itemValue;
7214
7325
  const compositeValue = {};
@@ -7228,10 +7339,9 @@ function updateContainerField(element, fieldPath, value, context) {
7228
7339
  });
7229
7340
  }
7230
7341
  });
7231
- const existingContainers = findDirectContainerRows(scopeRoot, fieldPath);
7232
- if (value.length !== existingContainers.length) {
7342
+ if (value.length !== rows.length) {
7233
7343
  console.warn(
7234
- `updateContainerField: Multiple container field "${fieldPath}" item count mismatch. Consider re-rendering for add/remove.`
7344
+ `updateContainerField: Multiple container field "${fieldPath}" received ${value.length} items for ${rows.length} rendered rows. Rows are not added or removed here \u2014 re-render for add/remove.`
7235
7345
  );
7236
7346
  }
7237
7347
  } else {
@@ -7290,7 +7400,7 @@ function renderGroupElement(element, ctx, wrapper, pathKey) {
7290
7400
  maxCount: (_b = element.repeat) == null ? void 0 : _b.max
7291
7401
  };
7292
7402
  if (containerElement.multiple) {
7293
- renderMultipleContainerElement(containerElement, ctx, wrapper);
7403
+ renderMultipleContainerElement(containerElement, ctx, wrapper, pathKey);
7294
7404
  } else {
7295
7405
  renderSingleContainerElement(containerElement, ctx, wrapper, pathKey);
7296
7406
  }
@@ -10358,15 +10468,19 @@ var componentRegistry = {
10358
10468
  function getComponentOperations(elementType) {
10359
10469
  return componentRegistry[elementType] || null;
10360
10470
  }
10471
+ function resolveOperations(element) {
10472
+ const isHiddenField = element.type !== "markdown" && (element.type === "hidden" || Boolean(element.hidden));
10473
+ return isHiddenField ? componentRegistry.hidden : getComponentOperations(element.type);
10474
+ }
10361
10475
  function validateElementWithComponent(element, key, context) {
10362
- const ops = getComponentOperations(element.type);
10476
+ const ops = resolveOperations(element);
10363
10477
  if (ops && ops.validate) {
10364
10478
  return ops.validate(element, key, context);
10365
10479
  }
10366
10480
  return null;
10367
10481
  }
10368
10482
  function updateElementWithComponent(element, fieldPath, value, context) {
10369
- const ops = getComponentOperations(element.type);
10483
+ const ops = resolveOperations(element);
10370
10484
  if (ops && ops.update) {
10371
10485
  ops.update(element, fieldPath, value, context);
10372
10486
  return true;
@@ -10378,6 +10492,10 @@ function updateElementWithComponent(element, fieldPath, value, context) {
10378
10492
  function showTooltip(tooltipId, button) {
10379
10493
  const tooltip = document.getElementById(tooltipId);
10380
10494
  if (!tooltip) return;
10495
+ if (!button.isConnected) {
10496
+ tooltip.remove();
10497
+ return;
10498
+ }
10381
10499
  const isCurrentlyVisible = !tooltip.classList.contains("hidden");
10382
10500
  document.querySelectorAll('[id^="tooltip-"]').forEach((t2) => {
10383
10501
  t2.classList.add("hidden");
@@ -10468,6 +10586,8 @@ function extractDOMValue(fieldPath, formRoot) {
10468
10586
  `[name="${fieldPath}"]:checked`
10469
10587
  );
10470
10588
  return checked ? checked.value : void 0;
10589
+ } else if (input.dataset.hiddenField) {
10590
+ return deserializeHiddenValue(input.value);
10471
10591
  } else {
10472
10592
  return input.value;
10473
10593
  }
@@ -10606,7 +10726,7 @@ function createFieldLabel(element) {
10606
10726
  }
10607
10727
  return title;
10608
10728
  }
10609
- function createInfoButton(element) {
10729
+ function createInfoButton(element, state) {
10610
10730
  const infoBtn = document.createElement("button");
10611
10731
  infoBtn.type = "button";
10612
10732
  infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
@@ -10618,6 +10738,7 @@ function createInfoButton(element) {
10618
10738
  tooltip.style.position = "fixed";
10619
10739
  tooltip.textContent = element.description || element.hint || "Field information";
10620
10740
  document.body.appendChild(tooltip);
10741
+ state.tooltipElements.add(tooltip);
10621
10742
  infoBtn.onclick = (e) => {
10622
10743
  e.preventDefault();
10623
10744
  e.stopPropagation();
@@ -10625,7 +10746,7 @@ function createInfoButton(element) {
10625
10746
  };
10626
10747
  return infoBtn;
10627
10748
  }
10628
- function createLabelContainer(element) {
10749
+ function createLabelContainer(element, state) {
10629
10750
  const label = document.createElement("div");
10630
10751
  label.className = "flex items-center";
10631
10752
  label.style.marginBottom = "var(--fb-label-margin-bottom, 2px)";
@@ -10633,7 +10754,7 @@ function createLabelContainer(element) {
10633
10754
  const title = createFieldLabel(element);
10634
10755
  label.appendChild(title);
10635
10756
  if (element.description || element.hint) {
10636
- const infoBtn = createInfoButton(element);
10757
+ const infoBtn = createInfoButton(element, state);
10637
10758
  label.appendChild(infoBtn);
10638
10759
  }
10639
10760
  return label;
@@ -10708,7 +10829,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
10708
10829
  break;
10709
10830
  case "container":
10710
10831
  if (isMultiple) {
10711
- renderMultipleContainerElement(element, ctx, wrapper);
10832
+ renderMultipleContainerElement(element, ctx, wrapper, pathKey);
10712
10833
  } else {
10713
10834
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
10714
10835
  }
@@ -10761,7 +10882,7 @@ function renderElement2(element, ctx) {
10761
10882
  wrapper.setAttribute("data-fb-width", element.width || "full");
10762
10883
  const ops = getComponentOperations(element.type);
10763
10884
  if (!(ops == null ? void 0 : ops.ownsLabel)) {
10764
- const label = createLabelContainer(element);
10885
+ const label = createLabelContainer(element, ctx.state);
10765
10886
  wrapper.appendChild(label);
10766
10887
  }
10767
10888
  const pathKey = pathJoin(ctx.path, element.key);
@@ -10800,6 +10921,8 @@ var defaultConfig = {
10800
10921
  en: {
10801
10922
  // UI texts
10802
10923
  removeElement: "Remove",
10924
+ removeRow: "Remove row",
10925
+ removeRowFrom: "Remove row from {label}",
10803
10926
  clickDragText: "Click or drag file",
10804
10927
  clickDragTextMultiple: "Click or drag files",
10805
10928
  noFileSelected: "No file selected",
@@ -10873,6 +10996,8 @@ var defaultConfig = {
10873
10996
  ru: {
10874
10997
  // UI texts
10875
10998
  removeElement: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
10999
+ removeRow: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
11000
+ removeRowFrom: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443 \u0438\u0437 \xAB{label}\xBB",
10876
11001
  clickDragText: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B",
10877
11002
  clickDragTextMultiple: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B",
10878
11003
  noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
@@ -10975,7 +11100,8 @@ function createInstanceState(config) {
10975
11100
  prefill: {},
10976
11101
  syntheticElementIds: /* @__PURE__ */ new WeakMap(),
10977
11102
  syntheticElementIdCounter: 0,
10978
- enableIfObservers: /* @__PURE__ */ new Set()
11103
+ enableIfObservers: /* @__PURE__ */ new Set(),
11104
+ tooltipElements: /* @__PURE__ */ new Set()
10979
11105
  };
10980
11106
  }
10981
11107
  function generateInstanceId() {
@@ -11262,6 +11388,18 @@ var exampleThemes = {
11262
11388
  };
11263
11389
 
11264
11390
  // src/instance/FormBuilderInstance.ts
11391
+ function findOwnField(scope, lookupKey, ownBoundary) {
11392
+ const matches = scope.querySelectorAll(
11393
+ `[data-field-key="${lookupKey}"]`
11394
+ );
11395
+ for (const el of Array.from(matches)) {
11396
+ const boundary = el.closest(
11397
+ "[data-container-item], [data-container]"
11398
+ );
11399
+ if (boundary === ownBoundary) return el;
11400
+ }
11401
+ return null;
11402
+ }
11265
11403
  var FormBuilderInstance = class {
11266
11404
  constructor(config) {
11267
11405
  this.instanceId = generateInstanceId();
@@ -11343,7 +11481,11 @@ var FormBuilderInstance = class {
11343
11481
  /**
11344
11482
  * Trigger onChange callbacks with debouncing
11345
11483
  * @param fieldPath - Optional field path for field-specific change events
11346
- * @param fieldValue - Optional field value for field-specific change events
11484
+ * @param fieldValue - Optional field value for field-specific change events.
11485
+ * When omitted while fieldPath is given, the value is read from the
11486
+ * freshly extracted form data at debounce time — used by structural
11487
+ * changes (multi-item add/remove), where the handler has no cheap
11488
+ * current value but the array is trivially derivable after the fact.
11347
11489
  */
11348
11490
  triggerOnChange(fieldPath, fieldValue) {
11349
11491
  if (this.state.config.readonly) return;
@@ -11356,12 +11498,49 @@ var FormBuilderInstance = class {
11356
11498
  if (this.state.config.onChange) {
11357
11499
  this.state.config.onChange(formData);
11358
11500
  }
11359
- if (this.state.config.onFieldChange && fieldPath !== void 0 && fieldValue !== void 0) {
11360
- this.state.config.onFieldChange(fieldPath, fieldValue, formData);
11501
+ if (this.state.config.onFieldChange && fieldPath !== void 0) {
11502
+ const resolvedValue = fieldValue !== void 0 ? fieldValue : this.resolveDomPathValue(formData.data, fieldPath);
11503
+ this.state.config.onFieldChange(fieldPath, resolvedValue, formData);
11361
11504
  }
11362
11505
  this.state.debounceTimer = null;
11363
11506
  }, this.state.config.debounceMs);
11364
11507
  }
11508
+ /**
11509
+ * Resolve a DOM field path against the extracted form data.
11510
+ *
11511
+ * A plain getValueByPath is wrong for paths inside a multiple container:
11512
+ * row markers keep gaps after a deletion (`s[2]` may be the first surviving
11513
+ * row) while the extracted array is re-packed contiguously — the naive
11514
+ * lookup would read a different row, or nothing. Each `[N]` segment is
11515
+ * mapped from its marker to the row's position among the container's
11516
+ * rendered rows, the same DOM order extraction used to build the array.
11517
+ * A bracketed segment that is not a container marker (a multi-value leaf
11518
+ * like `tags[1]`, whose indices are contiguous) falls back to the index.
11519
+ */
11520
+ resolveDomPathValue(data, domPath) {
11521
+ let cur = data;
11522
+ let domPrefix = "";
11523
+ for (const seg of domPath.split(".")) {
11524
+ if (cur === null || cur === void 0) return void 0;
11525
+ const marker = seg.match(/^(.+)\[(\d+)\]$/);
11526
+ if (!marker) {
11527
+ domPrefix = domPrefix ? `${domPrefix}.${seg}` : seg;
11528
+ cur = cur[seg];
11529
+ continue;
11530
+ }
11531
+ const key = marker[1];
11532
+ domPrefix = domPrefix ? `${domPrefix}.${key}` : key;
11533
+ const arr = cur[key];
11534
+ if (!Array.isArray(arr)) return void 0;
11535
+ const rows = this.state.formRoot ? findDirectContainerRows(this.state.formRoot, domPrefix) : [];
11536
+ domPrefix = `${domPrefix}[${marker[2]}]`;
11537
+ const pos = rows.findIndex(
11538
+ (row) => row.getAttribute("data-container-item") === domPrefix
11539
+ );
11540
+ cur = pos >= 0 ? arr[pos] : arr[parseInt(marker[2], 10)];
11541
+ }
11542
+ return cur;
11543
+ }
11365
11544
  /**
11366
11545
  * Register an external action that will be displayed as a button
11367
11546
  * External actions can be form-level (no related_field) or field-level (with related_field)
@@ -11401,21 +11580,10 @@ var FormBuilderInstance = class {
11401
11580
  */
11402
11581
  findFormElementByFieldPath(fieldPath) {
11403
11582
  if (!this.state.formRoot) return null;
11404
- let element = this.state.formRoot.querySelector(
11583
+ const element = this.state.formRoot.querySelector(
11405
11584
  `[name="${fieldPath}"]`
11406
11585
  );
11407
11586
  if (element) return element;
11408
- const variations = [
11409
- fieldPath,
11410
- fieldPath.replace(/\[(\d+)\]/g, "[$1]"),
11411
- fieldPath.replace(/\./g, "[") + "]".repeat((fieldPath.match(/\./g) || []).length)
11412
- ];
11413
- for (const variation of variations) {
11414
- element = this.state.formRoot.querySelector(
11415
- `[name="${variation}"]`
11416
- );
11417
- if (element) return element;
11418
- }
11419
11587
  const schemaElement = this.findSchemaElement(fieldPath);
11420
11588
  if (!schemaElement) return null;
11421
11589
  const fieldWrappers = this.state.formRoot.querySelectorAll(".fb-field-wrapper");
@@ -11665,6 +11833,7 @@ var FormBuilderInstance = class {
11665
11833
  return;
11666
11834
  }
11667
11835
  this.disconnectEnableIfObservers();
11836
+ this.removeTooltipElements();
11668
11837
  this.state.formRoot = root;
11669
11838
  this.state.schema = schema;
11670
11839
  this.state.externalActions = actions || null;
@@ -11750,7 +11919,6 @@ var FormBuilderInstance = class {
11750
11919
  };
11751
11920
  setValidateElement(validateElement2);
11752
11921
  this.state.schema.elements.forEach((element) => {
11753
- var _a;
11754
11922
  if (element.enableIf) {
11755
11923
  try {
11756
11924
  const shouldEnable = evaluateEnableCondition(element.enableIf, data);
@@ -11767,24 +11935,12 @@ var FormBuilderInstance = class {
11767
11935
  if (element.type === "markdown") {
11768
11936
  return;
11769
11937
  }
11770
- if (element.hidden || element.type === "hidden") {
11771
- const hiddenInput = this.state.formRoot.querySelector(
11772
- `input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
11773
- );
11774
- const raw = (_a = hiddenInput == null ? void 0 : hiddenInput.value) != null ? _a : "";
11775
- if (raw !== "") {
11776
- data[element.key] = deserializeHiddenValue(raw);
11777
- } else {
11778
- data[element.key] = element.default !== void 0 ? element.default : null;
11779
- }
11780
- } else {
11781
- const result = validateElement2(element, { path: "" });
11782
- if (result.skip) return;
11783
- if (result.spread && result.value !== null && typeof result.value === "object") {
11784
- Object.assign(data, result.value);
11785
- } else if (element.key) {
11786
- data[element.key] = result.value;
11787
- }
11938
+ const result = validateElement2(element, { path: "" });
11939
+ if (result.skip) return;
11940
+ if (result.spread && result.value !== null && typeof result.value === "object") {
11941
+ Object.assign(data, result.value);
11942
+ } else if (element.key) {
11943
+ data[element.key] = result.value;
11788
11944
  }
11789
11945
  });
11790
11946
  return {
@@ -11988,29 +12144,29 @@ var FormBuilderInstance = class {
11988
12144
  const formRoot = this.state.formRoot;
11989
12145
  const lookupKey = getElementLookupKey(element, this.state);
11990
12146
  if (!currentPath) {
11991
- return formRoot.querySelector(`[data-field-key="${lookupKey}"]`);
12147
+ return findOwnField(formRoot, lookupKey, null);
11992
12148
  }
11993
12149
  const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
11994
12150
  if (pathMatch) {
11995
12151
  const containerEl2 = formRoot.querySelector(
11996
12152
  `[data-container-item="${pathMatch[1]}[${pathMatch[2]}]"]`
11997
12153
  );
11998
- return containerEl2 ? containerEl2.querySelector(`[data-field-key="${lookupKey}"]`) : null;
12154
+ return containerEl2 ? findOwnField(containerEl2, lookupKey, containerEl2) : null;
11999
12155
  }
12000
12156
  const containerEl = formRoot.querySelector(
12001
12157
  `[data-container="${currentPath}"]`
12002
12158
  );
12003
- return containerEl ? containerEl.querySelector(`[data-field-key="${lookupKey}"]`) : null;
12159
+ return containerEl ? findOwnField(containerEl, lookupKey, containerEl) : null;
12004
12160
  }
12005
12161
  /**
12006
12162
  * Apply enableIf show/hide logic to a single field wrapper.
12007
12163
  * Extracted to reduce cyclomatic complexity of checkElements.
12008
12164
  */
12009
- applyEnableIfVisibility(element, wrapper, currentPath, fullPath, formData) {
12165
+ applyEnableIfVisibility(element, wrapper, domPath, dataPath, fullDomPath, formData) {
12010
12166
  var _a, _b, _c, _d;
12011
12167
  try {
12012
12168
  const scope = (_a = element.enableIf.scope) != null ? _a : "relative";
12013
- const containerData = scope === "relative" && currentPath ? getValueByPath(formData, currentPath) : void 0;
12169
+ const containerData = scope === "relative" && dataPath ? getValueByPath(formData, dataPath) : void 0;
12014
12170
  const shouldEnable = evaluateEnableCondition(
12015
12171
  element.enableIf,
12016
12172
  formData,
@@ -12018,10 +12174,12 @@ var FormBuilderInstance = class {
12018
12174
  );
12019
12175
  const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
12020
12176
  if (shouldEnable && isCurrentlyDisabled) {
12021
- const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
12177
+ const containerPrefill = dataPath ? getValueByPath(formData, dataPath) : formData;
12022
12178
  const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
12023
12179
  const newWrapper = renderElement2(element, {
12024
- path: currentPath,
12180
+ // DOM path: the re-rendered control's `name` must match the row it
12181
+ // lives in, not the row's position in the extracted array.
12182
+ path: domPath,
12025
12183
  prefill: prefillContext,
12026
12184
  formData,
12027
12185
  state: this.state,
@@ -12041,7 +12199,7 @@ var FormBuilderInstance = class {
12041
12199
  }
12042
12200
  } catch (error) {
12043
12201
  console.error(
12044
- `Error re-evaluating enableIf for field "${(_d = element.key) != null ? _d : "<no key>"}" at path "${fullPath}":`,
12202
+ `Error re-evaluating enableIf for field "${(_d = element.key) != null ? _d : "<no key>"}" at path "${fullDomPath}":`,
12045
12203
  error
12046
12204
  );
12047
12205
  }
@@ -12053,40 +12211,47 @@ var FormBuilderInstance = class {
12053
12211
  reevaluateConditionalFields() {
12054
12212
  if (!this.state.schema || !this.state.formRoot) return;
12055
12213
  const formData = this.validateForm(true).data;
12056
- const checkElements = (elements, currentPath) => {
12214
+ const checkElements = (elements, domPath, dataPath) => {
12057
12215
  elements.forEach((element) => {
12058
- var _a, _b;
12059
- const fullPath = currentPath ? `${currentPath}.${(_a = element.key) != null ? _a : ""}` : (_b = element.key) != null ? _b : "";
12216
+ var _a;
12217
+ const key = (_a = element.key) != null ? _a : "";
12218
+ const fullDomPath = domPath ? `${domPath}.${key}` : key;
12219
+ const fullDataPath = dataPath ? `${dataPath}.${key}` : key;
12060
12220
  if (element.enableIf) {
12061
- const fieldWrapper = this.findFieldWrapper(element, currentPath);
12221
+ const fieldWrapper = this.findFieldWrapper(element, domPath);
12062
12222
  if (fieldWrapper) {
12063
12223
  this.applyEnableIfVisibility(
12064
12224
  element,
12065
12225
  fieldWrapper,
12066
- currentPath,
12067
- fullPath,
12226
+ domPath,
12227
+ dataPath,
12228
+ fullDomPath,
12068
12229
  formData
12069
12230
  );
12070
12231
  }
12071
12232
  }
12072
12233
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
12073
- const containerData = element.key ? getValueByPath(formData, fullPath) : void 0;
12234
+ const containerData = element.key ? getValueByPath(formData, fullDataPath) : void 0;
12074
12235
  if (Array.isArray(containerData)) {
12075
12236
  const directItems = findDirectContainerRows(
12076
12237
  this.state.formRoot,
12077
- fullPath
12238
+ fullDomPath
12078
12239
  );
12079
- directItems.forEach((el) => {
12080
- const attr = el.getAttribute("data-container-item") || "";
12081
- checkElements(element.elements, attr);
12240
+ directItems.forEach((el, rowIndex) => {
12241
+ const marker = el.getAttribute("data-container-item") || "";
12242
+ checkElements(
12243
+ element.elements,
12244
+ marker,
12245
+ `${fullDataPath}[${rowIndex}]`
12246
+ );
12082
12247
  });
12083
12248
  } else {
12084
- checkElements(element.elements, fullPath);
12249
+ checkElements(element.elements, fullDomPath, fullDataPath);
12085
12250
  }
12086
12251
  }
12087
12252
  });
12088
12253
  };
12089
- checkElements(this.state.schema.elements, "");
12254
+ checkElements(this.state.schema.elements, "", "");
12090
12255
  }
12091
12256
  /**
12092
12257
  * Destroy instance and clean up resources
@@ -12098,6 +12263,7 @@ var FormBuilderInstance = class {
12098
12263
  this.state.debounceTimer = null;
12099
12264
  }
12100
12265
  this.disconnectEnableIfObservers();
12266
+ this.removeTooltipElements();
12101
12267
  this.state.resourceIndex.clear();
12102
12268
  if (this.state.formRoot) {
12103
12269
  clear(this.state.formRoot);
@@ -12115,6 +12281,12 @@ var FormBuilderInstance = class {
12115
12281
  }
12116
12282
  this.state.enableIfObservers.clear();
12117
12283
  }
12284
+ removeTooltipElements() {
12285
+ for (const tooltip of this.state.tooltipElements) {
12286
+ tooltip.remove();
12287
+ }
12288
+ this.state.tooltipElements.clear();
12289
+ }
12118
12290
  };
12119
12291
 
12120
12292
  // src/index.ts