@dmitryvim/form-builder 0.5.3 → 0.6.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.
@@ -4,15 +4,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  // src/utils/translation.ts
6
6
  function t(key, state, params) {
7
+ var _a, _b;
7
8
  const locale = state.config.locale || "en";
8
9
  const localeTranslations = state.config.translations[locale];
9
10
  const fallbackTranslations = state.config.translations.en;
10
- let text = (localeTranslations == null ? void 0 : localeTranslations[key]) || (fallbackTranslations == null ? void 0 : fallbackTranslations[key]) || key;
11
+ let text = (_b = (_a = localeTranslations == null ? void 0 : localeTranslations[key]) != null ? _a : fallbackTranslations == null ? void 0 : fallbackTranslations[key]) != null ? _b : key;
11
12
  if (params) {
12
13
  for (const [paramKey, paramValue] of Object.entries(params)) {
13
14
  text = text.replace(
14
15
  new RegExp(`\\{${paramKey}\\}`, "g"),
15
- String(paramValue)
16
+ () => String(paramValue)
16
17
  );
17
18
  }
18
19
  }
@@ -27,9 +28,7 @@ function isPlainObject(obj) {
27
28
  return obj && typeof obj === "object" && obj.constructor === Object;
28
29
  }
29
30
  function escapeHtml(text) {
30
- const div = document.createElement("div");
31
- div.textContent = text;
32
- return div.innerHTML;
31
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
33
32
  }
34
33
  function getElementLookupKey(element, state) {
35
34
  if (element.key) {
@@ -64,8 +63,8 @@ function formatFileSize(bytes) {
64
63
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
65
64
  }
66
65
  function serializeHiddenValue(value) {
67
- if (value === null || value === void 0) return "";
68
- return typeof value === "object" ? JSON.stringify(value) : String(value);
66
+ if (value === void 0) return "";
67
+ return JSON.stringify(value);
69
68
  }
70
69
  function deserializeHiddenValue(raw) {
71
70
  if (raw === "") return null;
@@ -75,6 +74,23 @@ function deserializeHiddenValue(raw) {
75
74
  return raw;
76
75
  }
77
76
  }
77
+ function readTypedInputValue(input) {
78
+ if (input instanceof HTMLInputElement) {
79
+ if (input.type === "checkbox") return input.checked;
80
+ if (input.dataset.hiddenField) return deserializeHiddenValue(input.value);
81
+ if (input.dataset.booleanField) return input.value === "true";
82
+ if (input.type === "number" || input.type === "range") {
83
+ if (input.value === "") return null;
84
+ const parsed = parseFloat(input.value);
85
+ const decimals = input.dataset.decimals;
86
+ return decimals !== void 0 ? Number(parsed.toFixed(parseInt(decimals, 10))) : parsed;
87
+ }
88
+ if (input.dataset.colourField) {
89
+ return input.value.toUpperCase();
90
+ }
91
+ }
92
+ return input.value === "" ? null : input.value;
93
+ }
78
94
  function createHiddenInput(name, value) {
79
95
  const input = document.createElement("input");
80
96
  input.type = "hidden";
@@ -316,6 +332,14 @@ function validateSchema(schema) {
316
332
  }
317
333
  }
318
334
  function validateElements(elements, path) {
335
+ const seenKeys = /* @__PURE__ */ new Set();
336
+ elements.forEach((element, index) => {
337
+ if (!element.key) return;
338
+ if (seenKeys.has(element.key)) {
339
+ errors.push(`${path}[${index}]: duplicate key "${element.key}"`);
340
+ }
341
+ seenKeys.add(element.key);
342
+ });
319
343
  elements.forEach((element, index) => {
320
344
  const elementPath = `${path}[${index}]`;
321
345
  if (!element.type) {
@@ -325,6 +349,14 @@ function validateSchema(schema) {
325
349
  errors.push(`${elementPath}: missing key`);
326
350
  }
327
351
  validateCountBounds(element, elementPath, errors);
352
+ if (element.type === "number" && "decimals" in element) {
353
+ const decimals = element.decimals;
354
+ if (decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0)) {
355
+ errors.push(
356
+ `${elementPath}: decimals must be a non-negative integer (got ${JSON.stringify(decimals)})`
357
+ );
358
+ }
359
+ }
328
360
  if (element.type === "markdown") {
329
361
  const content = element.content;
330
362
  if (typeof content !== "string") {
@@ -476,12 +508,58 @@ function deepEqual(a, b) {
476
508
  }
477
509
 
478
510
  // src/utils/styles.ts
479
- function clearFieldError(input) {
511
+ function findErrorAnchor(input) {
512
+ var _a, _b, _c, _d;
513
+ return (_d = (_c = (_a = input.closest) == null ? void 0 : _a.call(input, ".fb-chip")) != null ? _c : (_b = input.closest) == null ? void 0 : _b.call(input, ".slider-container")) != null ? _d : input;
514
+ }
515
+ function findErrorNode(input) {
516
+ const anchor = findErrorAnchor(input);
480
517
  const name = input.getAttribute("name");
481
- if (!name) return;
482
- const doc = input.ownerDocument || document;
483
- const errorNode = doc.getElementById(`error-${name}`);
484
- if (errorNode) errorNode.remove();
518
+ const parent = anchor.parentElement;
519
+ if (name && parent) {
520
+ for (const child of Array.from(parent.children)) {
521
+ if (child.classList.contains("error-message") && child.getAttribute("data-error-for") === name) {
522
+ return child;
523
+ }
524
+ }
525
+ }
526
+ const sibling = anchor.nextElementSibling;
527
+ return sibling && sibling.classList.contains("error-message") ? sibling : null;
528
+ }
529
+ function markFieldValidity(input, errorMessage) {
530
+ var _a, _b, _c, _d;
531
+ if (!input) return;
532
+ if (errorMessage == null) {
533
+ input.classList.remove("invalid");
534
+ input.title = "";
535
+ (_a = findErrorNode(input)) == null ? void 0 : _a.remove();
536
+ return;
537
+ }
538
+ input.classList.add("invalid");
539
+ input.title = errorMessage;
540
+ if (errorMessage === "") {
541
+ (_b = findErrorNode(input)) == null ? void 0 : _b.remove();
542
+ return;
543
+ }
544
+ let errorElement = findErrorNode(input);
545
+ if (!errorElement) {
546
+ const anchor = findErrorAnchor(input);
547
+ errorElement = document.createElement("div");
548
+ errorElement.className = "error-message";
549
+ errorElement.style.cssText = `
550
+ color: var(--fb-error-color);
551
+ font-size: var(--fb-font-size-small);
552
+ margin-top: 0.25rem;
553
+ `;
554
+ (_c = anchor.parentNode) == null ? void 0 : _c.insertBefore(errorElement, anchor.nextSibling);
555
+ }
556
+ errorElement.setAttribute("data-error-for", (_d = input.getAttribute("name")) != null ? _d : "");
557
+ errorElement.textContent = errorMessage;
558
+ errorElement.style.display = "block";
559
+ }
560
+ function clearFieldError(input) {
561
+ var _a;
562
+ (_a = findErrorNode(input)) == null ? void 0 : _a.remove();
485
563
  }
486
564
  var BIN_ICON_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
487
565
  function ensureThemingHooks(doc) {
@@ -603,8 +681,8 @@ function ensureThemingHooks(doc) {
603
681
  `;
604
682
  doc.head.appendChild(style);
605
683
  }
606
- function applyAutoExpand(textarea, options = {}) {
607
- var _a;
684
+ function applyAutoExpand(textarea, options) {
685
+ var _a, _b;
608
686
  textarea.style.overflow = "hidden";
609
687
  textarea.style.resize = "none";
610
688
  const minRows = Math.max(1, (_a = options.minRows) != null ? _a : 1);
@@ -632,18 +710,20 @@ function applyAutoExpand(textarea, options = {}) {
632
710
  if (typeof ResizeObserver === "undefined") return;
633
711
  let lastWidth = -1;
634
712
  const ro = new ResizeObserver((entries) => {
635
- var _a2, _b, _c, _d, _e;
713
+ var _a2, _b2, _c, _d, _e, _f;
636
714
  if (!textarea.isConnected) {
637
715
  ro.disconnect();
716
+ (_a2 = options.observers) == null ? void 0 : _a2.delete(ro);
638
717
  return;
639
718
  }
640
719
  const entry = entries[0];
641
- const w = (_e = (_d = (_b = (_a2 = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _a2[0]) == null ? void 0 : _b.inlineSize) != null ? _d : (_c = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _c.width) != null ? _e : 0;
720
+ const w = (_f = (_e = (_c = (_b2 = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _b2[0]) == null ? void 0 : _c.inlineSize) != null ? _e : (_d = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _d.width) != null ? _f : 0;
642
721
  if (w === lastWidth) return;
643
722
  lastWidth = w;
644
723
  resize();
645
724
  });
646
725
  ro.observe(textarea);
726
+ (_b = options.observers) == null ? void 0 : _b.add(ro);
647
727
  }
648
728
  function applySingleLineMode(textarea) {
649
729
  textarea.addEventListener("keydown", (e) => {
@@ -931,7 +1011,7 @@ function createCharCounter(element, input) {
931
1011
  return counter;
932
1012
  }
933
1013
  function renderTextElement(element, ctx, wrapper, pathKey) {
934
- var _a;
1014
+ var _a, _b, _c;
935
1015
  const state = ctx.state;
936
1016
  const readonly = isElementReadonly(element, state, ctx);
937
1017
  const inputWrapper = document.createElement("div");
@@ -958,11 +1038,11 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
958
1038
  overflow-wrap: anywhere;
959
1039
  `;
960
1040
  textInput.name = pathKey;
961
- textInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
962
- textInput.value = ctx.prefill[element.key] || element.default || "";
1041
+ textInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
1042
+ textInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
963
1043
  textInput.readOnly = readonly;
964
1044
  applySingleLineMode(textInput);
965
- applyAutoExpand(textInput);
1045
+ applyAutoExpand(textInput, { observers: state.autoExpandObservers });
966
1046
  if (!readonly) {
967
1047
  textInput.addEventListener("focus", () => {
968
1048
  textInput.style.borderColor = "var(--fb-border-focus-color)";
@@ -1021,11 +1101,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1021
1101
  const chip = input.closest(".fb-chip");
1022
1102
  const sib = chip == null ? void 0 : chip.nextElementSibling;
1023
1103
  if (sib && sib.classList.contains("error-message")) {
1024
- sib.id = `error-${input.name}`;
1104
+ sib.setAttribute("data-error-for", input.name);
1025
1105
  }
1026
1106
  });
1027
1107
  }
1028
1108
  function addChip(value = "") {
1109
+ var _a2;
1029
1110
  const chip = document.createElement("div");
1030
1111
  chip.className = "fb-chip";
1031
1112
  const dot = document.createElement("span");
@@ -1036,7 +1117,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1036
1117
  input.rows = 1;
1037
1118
  input.className = "fb-chip-input";
1038
1119
  input.value = value;
1039
- input.placeholder = element.placeholder || t("placeholderText", state);
1120
+ input.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
1040
1121
  input.readOnly = readonly;
1041
1122
  chip.appendChild(input);
1042
1123
  if (!readonly && ctx.instance) {
@@ -1050,7 +1131,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1050
1131
  input.addEventListener("input", handleChange);
1051
1132
  }
1052
1133
  applySingleLineMode(input);
1053
- applyAutoExpand(input);
1134
+ applyAutoExpand(input, { observers: state.autoExpandObservers });
1054
1135
  if (!readonly) {
1055
1136
  const rem = document.createElement("button");
1056
1137
  rem.type = "button";
@@ -1058,6 +1139,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1058
1139
  rem.setAttribute("aria-label", t("removeElement", state));
1059
1140
  rem.innerHTML = BIN_ICON_SVG;
1060
1141
  rem.onclick = () => {
1142
+ var _a3;
1061
1143
  const chips = list.querySelectorAll(".fb-chip");
1062
1144
  const idx = Array.prototype.indexOf.call(chips, chip);
1063
1145
  if (idx < 0) return;
@@ -1071,6 +1153,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1071
1153
  updateIndices();
1072
1154
  updateAddButton();
1073
1155
  updateRemoveButtons();
1156
+ (_a3 = ctx.instance) == null ? void 0 : _a3.triggerOnChange(pathKey);
1074
1157
  };
1075
1158
  chip.appendChild(rem);
1076
1159
  }
@@ -1091,10 +1174,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1091
1174
  const handle = createAddItemRow(
1092
1175
  "text",
1093
1176
  () => {
1177
+ var _a2;
1094
1178
  values.push(element.default || "");
1095
1179
  addChip(element.default || "");
1096
1180
  updateAddButton();
1097
1181
  updateRemoveButtons();
1182
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1098
1183
  },
1099
1184
  { label: element.addLabel }
1100
1185
  );
@@ -1113,41 +1198,6 @@ function validateTextElement(element, key, context) {
1113
1198
  var _a, _b, _c;
1114
1199
  const errors = [];
1115
1200
  const { scopeRoot, skipValidation } = context;
1116
- const markValidity = (input, errorMessage) => {
1117
- var _a2, _b2, _c2;
1118
- if (!input) return;
1119
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
1120
- let errorElement = document.getElementById(errorId);
1121
- if (errorMessage) {
1122
- input.classList.add("invalid");
1123
- input.title = errorMessage;
1124
- if (!errorElement) {
1125
- errorElement = document.createElement("div");
1126
- errorElement.id = errorId;
1127
- errorElement.className = "error-message";
1128
- errorElement.style.cssText = `
1129
- color: var(--fb-error-color);
1130
- font-size: var(--fb-font-size-small);
1131
- margin-top: 0.25rem;
1132
- `;
1133
- const chipAncestor = (_a2 = input.closest) == null ? void 0 : _a2.call(input, ".fb-chip");
1134
- const anchor = chipAncestor || input;
1135
- if (anchor.nextSibling) {
1136
- (_b2 = anchor.parentNode) == null ? void 0 : _b2.insertBefore(errorElement, anchor.nextSibling);
1137
- } else {
1138
- (_c2 = anchor.parentNode) == null ? void 0 : _c2.appendChild(errorElement);
1139
- }
1140
- }
1141
- errorElement.textContent = errorMessage;
1142
- errorElement.style.display = "block";
1143
- } else {
1144
- input.classList.remove("invalid");
1145
- input.title = "";
1146
- if (errorElement) {
1147
- errorElement.remove();
1148
- }
1149
- }
1150
- };
1151
1201
  const validateTextInput = (input, val, fieldKey) => {
1152
1202
  let hasError = false;
1153
1203
  const { state } = context;
@@ -1155,12 +1205,12 @@ function validateTextElement(element, key, context) {
1155
1205
  if (element.minLength !== void 0 && element.minLength !== null && val.length < element.minLength) {
1156
1206
  const msg = t("minLength", state, { min: element.minLength });
1157
1207
  errors.push(`${fieldKey}: ${msg}`);
1158
- markValidity(input, msg);
1208
+ markFieldValidity(input, msg);
1159
1209
  hasError = true;
1160
1210
  } else if (element.maxLength !== void 0 && element.maxLength !== null && val.length > element.maxLength) {
1161
1211
  const msg = t("maxLength", state, { max: element.maxLength });
1162
1212
  errors.push(`${fieldKey}: ${msg}`);
1163
- markValidity(input, msg);
1213
+ markFieldValidity(input, msg);
1164
1214
  hasError = true;
1165
1215
  } else if (element.pattern) {
1166
1216
  try {
@@ -1168,19 +1218,19 @@ function validateTextElement(element, key, context) {
1168
1218
  if (!re.test(val)) {
1169
1219
  const msg = t("patternMismatch", state);
1170
1220
  errors.push(`${fieldKey}: ${msg}`);
1171
- markValidity(input, msg);
1221
+ markFieldValidity(input, msg);
1172
1222
  hasError = true;
1173
1223
  }
1174
1224
  } catch {
1175
1225
  const msg = t("invalidPattern", state);
1176
1226
  errors.push(`${fieldKey}: ${msg}`);
1177
- markValidity(input, msg);
1227
+ markFieldValidity(input, msg);
1178
1228
  hasError = true;
1179
1229
  }
1180
1230
  }
1181
1231
  }
1182
1232
  if (!hasError) {
1183
- markValidity(input, null);
1233
+ markFieldValidity(input, null);
1184
1234
  }
1185
1235
  };
1186
1236
  if (element.multiple) {
@@ -1211,12 +1261,12 @@ function validateTextElement(element, key, context) {
1211
1261
  }
1212
1262
  return { value: values, errors };
1213
1263
  } else {
1214
- const input = scopeRoot.querySelector(`[name$="${key}"]`);
1264
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
1215
1265
  const val = (_c = input == null ? void 0 : input.value) != null ? _c : "";
1216
1266
  if (!skipValidation && element.required && val === "") {
1217
1267
  const msg = t("required", context.state);
1218
1268
  errors.push(`${key}: ${msg}`);
1219
- markValidity(input, msg);
1269
+ markFieldValidity(input, msg);
1220
1270
  return { value: null, errors };
1221
1271
  }
1222
1272
  if (input) {
@@ -1265,7 +1315,7 @@ function updateTextField(element, fieldPath, value, context) {
1265
1315
 
1266
1316
  // src/components/textarea.ts
1267
1317
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
1268
- var _a, _b;
1318
+ var _a, _b, _c, _d;
1269
1319
  const state = ctx.state;
1270
1320
  const readonly = isElementReadonly(element, state, ctx);
1271
1321
  const textareaWrapper = document.createElement("div");
@@ -1279,8 +1329,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1279
1329
  line-height: var(--fb-line-height, 1.5);
1280
1330
  `;
1281
1331
  textareaInput.name = pathKey;
1282
- textareaInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
1283
- textareaInput.value = ctx.prefill[element.key] || element.default || "";
1332
+ textareaInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
1333
+ textareaInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
1284
1334
  textareaInput.readOnly = readonly;
1285
1335
  if (!readonly && ctx.instance) {
1286
1336
  const handleChange = () => {
@@ -1290,7 +1340,10 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1290
1340
  textareaInput.addEventListener("blur", handleChange);
1291
1341
  textareaInput.addEventListener("input", handleChange);
1292
1342
  }
1293
- applyAutoExpand(textareaInput, { minRows: (_b = element.rows) != null ? _b : 1 });
1343
+ applyAutoExpand(textareaInput, {
1344
+ minRows: (_d = element.rows) != null ? _d : 1,
1345
+ observers: state.autoExpandObservers
1346
+ });
1294
1347
  textareaWrapper.appendChild(textareaInput);
1295
1348
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1296
1349
  const counter = createCharCounter(element, textareaInput);
@@ -1322,7 +1375,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1322
1375
  });
1323
1376
  }
1324
1377
  function addTextareaItem(value = "", index = -1) {
1325
- var _a2;
1378
+ var _a2, _b2;
1326
1379
  const itemWrapper = document.createElement("div");
1327
1380
  itemWrapper.className = "multiple-textarea-item";
1328
1381
  const textareaContainer = document.createElement("div");
@@ -1335,7 +1388,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1335
1388
  font-family: var(--fb-font-family);
1336
1389
  line-height: var(--fb-line-height, 1.5);
1337
1390
  `;
1338
- textareaInput.placeholder = element.placeholder || t("placeholderText", state);
1391
+ textareaInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
1339
1392
  textareaInput.value = value;
1340
1393
  textareaInput.readOnly = readonly;
1341
1394
  if (!readonly && ctx.instance) {
@@ -1346,7 +1399,10 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1346
1399
  textareaInput.addEventListener("blur", handleChange);
1347
1400
  textareaInput.addEventListener("input", handleChange);
1348
1401
  }
1349
- applyAutoExpand(textareaInput, { minRows: (_a2 = element.rows) != null ? _a2 : 1 });
1402
+ applyAutoExpand(textareaInput, {
1403
+ minRows: (_b2 = element.rows) != null ? _b2 : 1,
1404
+ observers: state.autoExpandObservers
1405
+ });
1350
1406
  textareaContainer.appendChild(textareaInput);
1351
1407
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1352
1408
  const counter = createCharCounter(element, textareaInput);
@@ -1375,6 +1431,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1375
1431
  removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
1376
1432
  removeBtn.innerHTML = "\u2715";
1377
1433
  removeBtn.onclick = () => {
1434
+ var _a2;
1378
1435
  const currentIndex = Array.from(container.children).indexOf(
1379
1436
  item
1380
1437
  );
@@ -1384,6 +1441,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1384
1441
  updateIndices();
1385
1442
  updateAddButton();
1386
1443
  updateRemoveButtons();
1444
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1387
1445
  }
1388
1446
  };
1389
1447
  item.appendChild(removeBtn);
@@ -1399,10 +1457,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1399
1457
  const handle = createAddItemRow(
1400
1458
  "textarea",
1401
1459
  () => {
1460
+ var _a2;
1402
1461
  values.push(element.default || "");
1403
1462
  addTextareaItem(element.default || "");
1404
1463
  updateAddButton();
1405
1464
  updateRemoveButtons();
1465
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1406
1466
  },
1407
1467
  { label: element.addLabel }
1408
1468
  );
@@ -1565,7 +1625,19 @@ function createNumberRangeHint(element, input) {
1565
1625
  updateColor();
1566
1626
  return hint;
1567
1627
  }
1628
+ function numberStepAttr(element) {
1629
+ if (element.step !== void 0) return element.step.toString();
1630
+ if (element.decimals !== void 0)
1631
+ return (10 ** -element.decimals).toString();
1632
+ return "any";
1633
+ }
1634
+ function applyDecimalsMarker(input, element) {
1635
+ if (element.decimals !== void 0) {
1636
+ input.setAttribute("data-decimals", String(element.decimals));
1637
+ }
1638
+ }
1568
1639
  function renderNumberElement(element, ctx, wrapper, pathKey) {
1640
+ var _a, _b, _c;
1569
1641
  const state = ctx.state;
1570
1642
  const readonly = isElementReadonly(element, state, ctx);
1571
1643
  const inputWrapper = document.createElement("div");
@@ -1573,11 +1645,12 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1573
1645
  const numberInput = document.createElement("input");
1574
1646
  numberInput.type = "number";
1575
1647
  numberInput.name = pathKey;
1576
- numberInput.placeholder = element.placeholder || "0";
1648
+ numberInput.placeholder = (_a = element.placeholder) != null ? _a : "0";
1577
1649
  if (element.min !== void 0) numberInput.min = element.min.toString();
1578
1650
  if (element.max !== void 0) numberInput.max = element.max.toString();
1579
- if (element.step !== void 0) numberInput.step = element.step.toString();
1580
- numberInput.value = ctx.prefill[element.key] || element.default || "";
1651
+ numberInput.step = numberStepAttr(element);
1652
+ applyDecimalsMarker(numberInput, element);
1653
+ numberInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
1581
1654
  numberInput.readOnly = readonly;
1582
1655
  if (!element.stepper) {
1583
1656
  numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
@@ -1609,7 +1682,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1609
1682
  wrapper.appendChild(inputWrapper);
1610
1683
  }
1611
1684
  function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1612
- var _a, _b;
1685
+ var _a, _b, _c;
1613
1686
  const state = ctx.state;
1614
1687
  const readonly = isElementReadonly(element, state, ctx);
1615
1688
  const prefillValues = ctx.prefill[element.key] || [];
@@ -1617,7 +1690,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1617
1690
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
1618
1691
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
1619
1692
  while (values.length < minCount) {
1620
- values.push(element.default || "");
1693
+ values.push((_c = element.default) != null ? _c : "");
1621
1694
  }
1622
1695
  const container = document.createElement("div");
1623
1696
  container.className = "fb-row";
@@ -1632,6 +1705,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1632
1705
  });
1633
1706
  }
1634
1707
  function addNumberItem(value = "", index = -1) {
1708
+ var _a2;
1635
1709
  const itemWrapper = document.createElement("div");
1636
1710
  itemWrapper.className = "multiple-number-item flex items-center gap-2";
1637
1711
  const inputContainer = document.createElement("div");
@@ -1646,10 +1720,11 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1646
1720
  width: 100%;
1647
1721
  box-sizing: border-box;
1648
1722
  `;
1649
- numberInput.placeholder = element.placeholder || "0";
1723
+ numberInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : "0";
1650
1724
  if (element.min !== void 0) numberInput.min = element.min.toString();
1651
1725
  if (element.max !== void 0) numberInput.max = element.max.toString();
1652
- if (element.step !== void 0) numberInput.step = element.step.toString();
1726
+ numberInput.step = numberStepAttr(element);
1727
+ applyDecimalsMarker(numberInput, element);
1653
1728
  numberInput.value = value.toString();
1654
1729
  numberInput.readOnly = readonly;
1655
1730
  if (!readonly && ctx.instance) {
@@ -1688,6 +1763,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1688
1763
  removeBtn.className = "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
1689
1764
  removeBtn.innerHTML = "\u2715";
1690
1765
  removeBtn.onclick = () => {
1766
+ var _a2;
1691
1767
  const currentIndex = Array.from(container.children).indexOf(
1692
1768
  item
1693
1769
  );
@@ -1697,6 +1773,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1697
1773
  updateIndices();
1698
1774
  updateAddButton();
1699
1775
  updateRemoveButtons();
1776
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1700
1777
  }
1701
1778
  };
1702
1779
  item.appendChild(removeBtn);
@@ -1712,10 +1789,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1712
1789
  const handle = createAddItemRow(
1713
1790
  "number",
1714
1791
  () => {
1715
- values.push(element.default || "");
1716
- addNumberItem(element.default || "");
1792
+ var _a2, _b2, _c2;
1793
+ values.push((_a2 = element.default) != null ? _a2 : "");
1794
+ addNumberItem((_b2 = element.default) != null ? _b2 : "");
1717
1795
  updateAddButton();
1718
1796
  updateRemoveButtons();
1797
+ (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
1719
1798
  },
1720
1799
  { label: element.addLabel }
1721
1800
  );
@@ -1731,58 +1810,25 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1731
1810
  updateRemoveButtons();
1732
1811
  }
1733
1812
  function validateNumberElement(element, key, context) {
1734
- var _a, _b, _c, _d, _e;
1813
+ var _a, _b, _c;
1735
1814
  const errors = [];
1736
1815
  const { scopeRoot, skipValidation } = context;
1737
- const markValidity = (input, errorMessage) => {
1738
- var _a2, _b2;
1739
- if (!input) return;
1740
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
1741
- let errorElement = document.getElementById(errorId);
1742
- if (errorMessage) {
1743
- input.classList.add("invalid");
1744
- input.title = errorMessage;
1745
- if (!errorElement) {
1746
- errorElement = document.createElement("div");
1747
- errorElement.id = errorId;
1748
- errorElement.className = "error-message";
1749
- errorElement.style.cssText = `
1750
- color: var(--fb-error-color);
1751
- font-size: var(--fb-font-size-small);
1752
- margin-top: 0.25rem;
1753
- `;
1754
- if (input.nextSibling) {
1755
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
1756
- } else {
1757
- (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
1758
- }
1759
- }
1760
- errorElement.textContent = errorMessage;
1761
- errorElement.style.display = "block";
1762
- } else {
1763
- input.classList.remove("invalid");
1764
- input.title = "";
1765
- if (errorElement) {
1766
- errorElement.remove();
1767
- }
1768
- }
1769
- };
1770
1816
  const validateNumberInput = (input, v, fieldKey) => {
1771
1817
  let hasError = false;
1772
1818
  const { state } = context;
1773
1819
  if (!skipValidation && element.min !== void 0 && element.min !== null && v < element.min) {
1774
1820
  const msg = t("minValue", state, { min: element.min });
1775
1821
  errors.push(`${fieldKey}: ${msg}`);
1776
- markValidity(input, msg);
1822
+ markFieldValidity(input, msg);
1777
1823
  hasError = true;
1778
1824
  } else if (!skipValidation && element.max !== void 0 && element.max !== null && v > element.max) {
1779
1825
  const msg = t("maxValue", state, { max: element.max });
1780
1826
  errors.push(`${fieldKey}: ${msg}`);
1781
- markValidity(input, msg);
1827
+ markFieldValidity(input, msg);
1782
1828
  hasError = true;
1783
1829
  }
1784
1830
  if (!hasError) {
1785
- markValidity(input, null);
1831
+ markFieldValidity(input, null);
1786
1832
  }
1787
1833
  };
1788
1834
  if (element.multiple) {
@@ -1791,24 +1837,23 @@ function validateNumberElement(element, key, context) {
1791
1837
  );
1792
1838
  const values = [];
1793
1839
  inputs.forEach((input, index) => {
1794
- var _a2, _b2, _c2;
1840
+ var _a2;
1795
1841
  const raw = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
1796
1842
  if (raw === "") {
1797
1843
  values.push(null);
1798
- markValidity(input, null);
1844
+ markFieldValidity(input, null);
1799
1845
  return;
1800
1846
  }
1801
1847
  const v = parseFloat(raw);
1802
1848
  if (!skipValidation && !Number.isFinite(v)) {
1803
1849
  const msg = t("notANumber", context.state);
1804
1850
  errors.push(`${key}[${index}]: ${msg}`);
1805
- markValidity(input, msg);
1851
+ markFieldValidity(input, msg);
1806
1852
  values.push(null);
1807
1853
  return;
1808
1854
  }
1809
1855
  validateNumberInput(input, v, `${key}[${index}]`);
1810
- const d = Number.isInteger((_b2 = element.decimals) != null ? _b2 : 0) ? (_c2 = element.decimals) != null ? _c2 : 0 : 0;
1811
- values.push(Number(v.toFixed(d)));
1856
+ values.push(applyDecimals(v, element.decimals));
1812
1857
  });
1813
1858
  if (!skipValidation) {
1814
1859
  const { state } = context;
@@ -1827,31 +1872,34 @@ function validateNumberElement(element, key, context) {
1827
1872
  }
1828
1873
  return { value: values, errors };
1829
1874
  } else {
1830
- const input = scopeRoot.querySelector(`[name$="${key}"]`);
1875
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
1831
1876
  const raw = (_c = input == null ? void 0 : input.value) != null ? _c : "";
1832
1877
  const { state } = context;
1833
1878
  if (!skipValidation && element.required && raw === "") {
1834
1879
  const msg = t("required", state);
1835
1880
  errors.push(`${key}: ${msg}`);
1836
- markValidity(input, msg);
1881
+ markFieldValidity(input, msg);
1837
1882
  return { value: null, errors };
1838
1883
  }
1839
1884
  if (raw === "") {
1840
- markValidity(input, null);
1885
+ markFieldValidity(input, null);
1841
1886
  return { value: null, errors };
1842
1887
  }
1843
1888
  const v = parseFloat(raw);
1844
1889
  if (!skipValidation && !Number.isFinite(v)) {
1845
1890
  const msg = t("notANumber", state);
1846
1891
  errors.push(`${key}: ${msg}`);
1847
- markValidity(input, msg);
1892
+ markFieldValidity(input, msg);
1848
1893
  return { value: null, errors };
1849
1894
  }
1850
1895
  validateNumberInput(input, v, key);
1851
- const d = Number.isInteger((_d = element.decimals) != null ? _d : 0) ? (_e = element.decimals) != null ? _e : 0 : 0;
1852
- return { value: Number(v.toFixed(d)), errors };
1896
+ return { value: applyDecimals(v, element.decimals), errors };
1853
1897
  }
1854
1898
  }
1899
+ function applyDecimals(v, decimals) {
1900
+ if (!Number.isInteger(decimals) || decimals < 0) return v;
1901
+ return Number(v.toFixed(decimals));
1902
+ }
1855
1903
  function updateNumberField(element, fieldPath, value, context) {
1856
1904
  const { scopeRoot } = context;
1857
1905
  if (element.multiple) {
@@ -1891,7 +1939,41 @@ function updateNumberField(element, fieldPath, value, context) {
1891
1939
  }
1892
1940
 
1893
1941
  // src/components/select.ts
1942
+ function appendSelectOptions(select, element, selectedValue, state) {
1943
+ var _a;
1944
+ const options = element.options || [];
1945
+ if (!options.some((option) => option.value === "")) {
1946
+ const emptyOption = document.createElement("option");
1947
+ emptyOption.value = "";
1948
+ emptyOption.textContent = (_a = element.placeholder) != null ? _a : t("selectPlaceholder", state);
1949
+ select.appendChild(emptyOption);
1950
+ }
1951
+ const strSelected = selectedValue == null ? null : String(selectedValue);
1952
+ let anySelected = false;
1953
+ options.forEach((option) => {
1954
+ const optionEl = document.createElement("option");
1955
+ optionEl.value = option.value;
1956
+ optionEl.textContent = option.label;
1957
+ if (strSelected === option.value) {
1958
+ optionEl.selected = true;
1959
+ anySelected = true;
1960
+ }
1961
+ select.appendChild(optionEl);
1962
+ });
1963
+ if (!anySelected && strSelected !== null && strSelected !== "") {
1964
+ console.warn(
1965
+ `select "${element.key}": prefill value "${strSelected}" is not among the options; leaving the field unselected`
1966
+ );
1967
+ }
1968
+ if (!anySelected) {
1969
+ const empty = Array.from(select.options).find(
1970
+ (option) => option.value === ""
1971
+ );
1972
+ if (empty) empty.selected = true;
1973
+ }
1974
+ }
1894
1975
  function renderSelectElement(element, ctx, wrapper, pathKey) {
1976
+ var _a;
1895
1977
  const state = ctx.state;
1896
1978
  const readonly = isElementReadonly(element, state, ctx);
1897
1979
  const selectInput = document.createElement("select");
@@ -1903,18 +1985,18 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1903
1985
  `;
1904
1986
  selectInput.name = pathKey;
1905
1987
  selectInput.disabled = readonly;
1906
- (element.options || []).forEach((option) => {
1907
- const optionEl = document.createElement("option");
1908
- optionEl.value = option.value;
1909
- optionEl.textContent = option.label;
1910
- if ((ctx.prefill[element.key] || element.default) === option.value) {
1911
- optionEl.selected = true;
1912
- }
1913
- selectInput.appendChild(optionEl);
1914
- });
1988
+ appendSelectOptions(
1989
+ selectInput,
1990
+ element,
1991
+ (_a = ctx.prefill[element.key]) != null ? _a : element.default,
1992
+ state
1993
+ );
1915
1994
  if (!readonly && ctx.instance) {
1916
1995
  const handleChange = () => {
1917
- ctx.instance.triggerOnChange(pathKey, selectInput.value);
1996
+ ctx.instance.triggerOnChange(
1997
+ pathKey,
1998
+ selectInput.value === "" ? null : selectInput.value
1999
+ );
1918
2000
  };
1919
2001
  selectInput.addEventListener("change", handleChange);
1920
2002
  }
@@ -1927,7 +2009,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1927
2009
  }
1928
2010
  }
1929
2011
  function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1930
- var _a, _b, _c, _d;
2012
+ var _a, _b, _c;
1931
2013
  const state = ctx.state;
1932
2014
  const readonly = isElementReadonly(element, state, ctx);
1933
2015
  const prefillValues = ctx.prefill[element.key] || [];
@@ -1935,7 +2017,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1935
2017
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
1936
2018
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
1937
2019
  while (values.length < minCount) {
1938
- values.push(element.default || ((_d = (_c = element.options) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || "");
2020
+ values.push((_c = element.default) != null ? _c : "");
1939
2021
  }
1940
2022
  const container = document.createElement("div");
1941
2023
  container.className = "fb-row";
@@ -1960,15 +2042,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1960
2042
  font-family: var(--fb-font-family);
1961
2043
  `;
1962
2044
  selectInput.disabled = readonly;
1963
- (element.options || []).forEach((option) => {
1964
- const optionElement = document.createElement("option");
1965
- optionElement.value = option.value;
1966
- optionElement.textContent = option.label;
1967
- if (value === option.value) {
1968
- optionElement.selected = true;
1969
- }
1970
- selectInput.appendChild(optionElement);
1971
- });
2045
+ appendSelectOptions(selectInput, element, value, state);
1972
2046
  if (!readonly && ctx.instance) {
1973
2047
  const handleChange = () => {
1974
2048
  ctx.instance.triggerOnChange(selectInput.name, selectInput.value);
@@ -1998,6 +2072,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1998
2072
  removeBtn.className = "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
1999
2073
  removeBtn.innerHTML = "\u2715";
2000
2074
  removeBtn.onclick = () => {
2075
+ var _a2;
2001
2076
  const currentIndex = Array.from(container.children).indexOf(item);
2002
2077
  if (container.children.length > minCount) {
2003
2078
  values.splice(currentIndex, 1);
@@ -2005,6 +2080,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2005
2080
  updateIndices();
2006
2081
  updateAddButton();
2007
2082
  updateRemoveButtons();
2083
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
2008
2084
  }
2009
2085
  };
2010
2086
  item.appendChild(removeBtn);
@@ -2021,11 +2097,12 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2021
2097
  "select",
2022
2098
  () => {
2023
2099
  var _a2, _b2;
2024
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2100
+ const defaultValue = (_a2 = element.default) != null ? _a2 : "";
2025
2101
  values.push(defaultValue);
2026
2102
  addSelectItem(defaultValue);
2027
2103
  updateAddButton();
2028
2104
  updateRemoveButtons();
2105
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
2029
2106
  },
2030
2107
  { label: element.addLabel }
2031
2108
  );
@@ -2050,39 +2127,6 @@ function validateSelectElement(element, key, context) {
2050
2127
  var _a;
2051
2128
  const errors = [];
2052
2129
  const { scopeRoot, skipValidation } = context;
2053
- const markValidity = (input, errorMessage) => {
2054
- var _a2, _b;
2055
- if (!input) return;
2056
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2057
- let errorElement = document.getElementById(errorId);
2058
- if (errorMessage) {
2059
- input.classList.add("invalid");
2060
- input.title = errorMessage;
2061
- if (!errorElement) {
2062
- errorElement = document.createElement("div");
2063
- errorElement.id = errorId;
2064
- errorElement.className = "error-message";
2065
- errorElement.style.cssText = `
2066
- color: var(--fb-error-color);
2067
- font-size: var(--fb-font-size-small);
2068
- margin-top: 0.25rem;
2069
- `;
2070
- if (input.nextSibling) {
2071
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2072
- } else {
2073
- (_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
2074
- }
2075
- }
2076
- errorElement.textContent = errorMessage;
2077
- errorElement.style.display = "block";
2078
- } else {
2079
- input.classList.remove("invalid");
2080
- input.title = "";
2081
- if (errorElement) {
2082
- errorElement.remove();
2083
- }
2084
- }
2085
- };
2086
2130
  const validateMultipleCount = (key2, values, element2, filterFn) => {
2087
2131
  var _a2, _b;
2088
2132
  if (skipValidation) return;
@@ -2108,27 +2152,36 @@ function validateSelectElement(element, key, context) {
2108
2152
  inputs.forEach((input) => {
2109
2153
  var _a2;
2110
2154
  const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2111
- values.push(val);
2112
- markValidity(input, null);
2155
+ values.push(val === "" ? null : val);
2156
+ markFieldValidity(input, null);
2113
2157
  });
2114
- validateMultipleCount(key, values, element, (v) => v !== "");
2158
+ validateMultipleCount(key, values, element, (v) => v != null);
2115
2159
  return { value: values, errors };
2116
2160
  } else {
2117
- const input = scopeRoot.querySelector(
2118
- `[name$="${key}"]`
2119
- );
2161
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
2120
2162
  const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
2121
2163
  if (!skipValidation && element.required && val === "") {
2122
2164
  const msg = t("required", context.state);
2123
2165
  errors.push(`${key}: ${msg}`);
2124
- markValidity(input, msg);
2166
+ markFieldValidity(input, msg);
2125
2167
  return { value: null, errors };
2126
2168
  } else {
2127
- markValidity(input, null);
2169
+ markFieldValidity(input, null);
2128
2170
  }
2129
2171
  return { value: val === "" ? null : val, errors };
2130
2172
  }
2131
2173
  }
2174
+ function assertValueInOptions(select, strValue, fieldPath) {
2175
+ if (strValue === "") return;
2176
+ const match = Array.from(select.options).some(
2177
+ (option) => option.value === strValue
2178
+ );
2179
+ if (!match) {
2180
+ throw new Error(
2181
+ `updateSelectField: value "${strValue}" is not among the options of "${fieldPath}"`
2182
+ );
2183
+ }
2184
+ }
2132
2185
  function updateSelectField(element, fieldPath, value, context) {
2133
2186
  const { scopeRoot } = context;
2134
2187
  if ("multiple" in element && element.multiple) {
@@ -2143,10 +2196,17 @@ function updateSelectField(element, fieldPath, value, context) {
2143
2196
  );
2144
2197
  selects.forEach((select, index) => {
2145
2198
  if (index < value.length) {
2146
- select.value = value[index] != null ? String(value[index]) : "";
2199
+ const strValue = value[index] != null ? String(value[index]) : "";
2200
+ assertValueInOptions(select, strValue, `${fieldPath}[${index}]`);
2201
+ }
2202
+ });
2203
+ selects.forEach((select, index) => {
2204
+ if (index < value.length) {
2205
+ const strValue = value[index] != null ? String(value[index]) : "";
2206
+ select.value = strValue;
2147
2207
  const options = select.querySelectorAll("option");
2148
2208
  options.forEach((option) => {
2149
- option.selected = option.value === String(value[index]);
2209
+ option.selected = option.value === strValue;
2150
2210
  });
2151
2211
  select.classList.remove("invalid");
2152
2212
  select.title = "";
@@ -2163,10 +2223,12 @@ function updateSelectField(element, fieldPath, value, context) {
2163
2223
  `[name="${fieldPath}"]`
2164
2224
  );
2165
2225
  if (select) {
2166
- select.value = value != null ? String(value) : "";
2226
+ const strValue = value != null ? String(value) : "";
2227
+ assertValueInOptions(select, strValue, fieldPath);
2228
+ select.value = strValue;
2167
2229
  const options = select.querySelectorAll("option");
2168
2230
  options.forEach((option) => {
2169
- option.selected = option.value === String(value);
2231
+ option.selected = option.value === strValue;
2170
2232
  });
2171
2233
  select.classList.remove("invalid");
2172
2234
  select.title = "";
@@ -2364,14 +2426,14 @@ function renderSwitcherElement(element, ctx, wrapper, pathKey) {
2364
2426
  }
2365
2427
  }
2366
2428
  function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2367
- var _a, _b, _c, _d;
2429
+ var _a, _b, _c;
2368
2430
  const state = ctx.state;
2369
2431
  const prefillValues = ctx.prefill[element.key] || [];
2370
2432
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2371
2433
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
2372
2434
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2373
2435
  while (values.length < minCount) {
2374
- values.push(element.default || ((_d = (_c = element.options) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || "");
2436
+ values.push((_c = element.default) != null ? _c : "");
2375
2437
  }
2376
2438
  const readonly = isElementReadonly(element, state, ctx);
2377
2439
  const container = document.createElement("div");
@@ -2440,6 +2502,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2440
2502
  removeBtn.style.backgroundColor = "transparent";
2441
2503
  });
2442
2504
  removeBtn.onclick = () => {
2505
+ var _a2;
2443
2506
  const currentIndex = Array.from(container.children).indexOf(
2444
2507
  item
2445
2508
  );
@@ -2449,6 +2512,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2449
2512
  updateIndices();
2450
2513
  updateAddButton();
2451
2514
  updateRemoveButtons();
2515
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
2452
2516
  }
2453
2517
  };
2454
2518
  item.appendChild(removeBtn);
@@ -2465,11 +2529,12 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2465
2529
  "switcher",
2466
2530
  () => {
2467
2531
  var _a2, _b2;
2468
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2532
+ const defaultValue = (_a2 = element.default) != null ? _a2 : "";
2469
2533
  values.push(defaultValue);
2470
2534
  addSwitcherItem(defaultValue);
2471
2535
  updateAddButton();
2472
2536
  updateRemoveButtons();
2537
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
2473
2538
  },
2474
2539
  { label: element.addLabel }
2475
2540
  );
@@ -2494,39 +2559,6 @@ function validateSwitcherElement(element, key, context) {
2494
2559
  var _a;
2495
2560
  const errors = [];
2496
2561
  const { scopeRoot, skipValidation } = context;
2497
- const markValidity = (input, errorMessage) => {
2498
- var _a2, _b;
2499
- if (!input) return;
2500
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2501
- let errorElement = document.getElementById(errorId);
2502
- if (errorMessage) {
2503
- input.classList.add("invalid");
2504
- input.title = errorMessage;
2505
- if (!errorElement) {
2506
- errorElement = document.createElement("div");
2507
- errorElement.id = errorId;
2508
- errorElement.className = "error-message";
2509
- errorElement.style.cssText = `
2510
- color: var(--fb-error-color);
2511
- font-size: var(--fb-font-size-small);
2512
- margin-top: 0.25rem;
2513
- `;
2514
- if (input.nextSibling) {
2515
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2516
- } else {
2517
- (_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
2518
- }
2519
- }
2520
- errorElement.textContent = errorMessage;
2521
- errorElement.style.display = "block";
2522
- } else {
2523
- input.classList.remove("invalid");
2524
- input.title = "";
2525
- if (errorElement) {
2526
- errorElement.remove();
2527
- }
2528
- }
2529
- };
2530
2562
  const validateMultipleCount = (fieldKey, values, el, filterFn) => {
2531
2563
  var _a2, _b;
2532
2564
  if (skipValidation) return;
@@ -2555,35 +2587,35 @@ function validateSwitcherElement(element, key, context) {
2555
2587
  inputs.forEach((input) => {
2556
2588
  var _a2;
2557
2589
  const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2558
- values.push(val);
2590
+ values.push(val === "" ? null : val);
2559
2591
  if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
2560
2592
  const msg = t("invalidOption", context.state);
2561
- markValidity(input, msg);
2593
+ markFieldValidity(input, msg);
2562
2594
  errors.push(`${key}: ${msg}`);
2563
2595
  } else {
2564
- markValidity(input, null);
2596
+ markFieldValidity(input, null);
2565
2597
  }
2566
2598
  });
2567
- validateMultipleCount(key, values, element, (v) => v !== "");
2599
+ validateMultipleCount(key, values, element, (v) => v != null);
2568
2600
  return { value: values, errors };
2569
2601
  } else {
2570
2602
  const input = scopeRoot.querySelector(
2571
- `input[type="hidden"][name$="${key}"]`
2603
+ `input[type="hidden"][name="${key}"]`
2572
2604
  );
2573
2605
  const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
2574
2606
  if (!skipValidation && element.required && val === "") {
2575
2607
  const msg = t("required", context.state);
2576
2608
  errors.push(`${key}: ${msg}`);
2577
- markValidity(input, msg);
2609
+ markFieldValidity(input, msg);
2578
2610
  return { value: null, errors };
2579
2611
  }
2580
2612
  if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
2581
2613
  const msg = t("invalidOption", context.state);
2582
2614
  errors.push(`${key}: ${msg}`);
2583
- markValidity(input, msg);
2615
+ markFieldValidity(input, msg);
2584
2616
  return { value: null, errors };
2585
2617
  }
2586
- markValidity(input, null);
2618
+ markFieldValidity(input, null);
2587
2619
  return { value: val === "" ? null : val, errors };
2588
2620
  }
2589
2621
  }
@@ -2758,6 +2790,7 @@ function renderBooleanElement(element, ctx, wrapper, pathKey) {
2758
2790
  const hiddenInput = document.createElement("input");
2759
2791
  hiddenInput.type = "hidden";
2760
2792
  hiddenInput.name = pathKey;
2793
+ hiddenInput.setAttribute("data-boolean-field", "true");
2761
2794
  hiddenInput.value = initial ? "true" : "false";
2762
2795
  const row = document.createElement("div");
2763
2796
  row.className = "fb-toggle-row";
@@ -5664,7 +5697,7 @@ function validateSingleFile(element, key, context) {
5664
5697
  const { scopeRoot, skipValidation, state } = context;
5665
5698
  const errors = [];
5666
5699
  const input = scopeRoot.querySelector(
5667
- `input[name$="${key}"][type="hidden"]`
5700
+ `input[name="${key}"][type="hidden"]`
5668
5701
  );
5669
5702
  const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
5670
5703
  if (!skipValidation && element.required && rid === "") {
@@ -5884,7 +5917,7 @@ function createReadonlyColourUI(value) {
5884
5917
  container.appendChild(hexText);
5885
5918
  return container;
5886
5919
  }
5887
- function createEditColourUI(value, pathKey, ctx) {
5920
+ function createEditColourUI(value, pathKey, ctx, placeholder) {
5888
5921
  const normalizedValue = normalizeColourValue(value);
5889
5922
  const pickerWrapper = document.createElement("div");
5890
5923
  pickerWrapper.className = "colour-picker-wrapper";
@@ -5908,9 +5941,10 @@ function createEditColourUI(value, pathKey, ctx) {
5908
5941
  const hexInput = document.createElement("input");
5909
5942
  hexInput.type = "text";
5910
5943
  hexInput.className = "colour-hex-input";
5944
+ hexInput.setAttribute("data-colour-field", "true");
5911
5945
  hexInput.name = pathKey;
5912
5946
  hexInput.value = normalizedValue;
5913
- hexInput.placeholder = "#000000";
5947
+ hexInput.placeholder = placeholder != null ? placeholder : "#000000";
5914
5948
  hexInput.style.cssText = `
5915
5949
  width: 100px;
5916
5950
  padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
@@ -5997,14 +6031,20 @@ function createEditColourUI(value, pathKey, ctx) {
5997
6031
  return pickerWrapper;
5998
6032
  }
5999
6033
  function renderColourElement(element, ctx, wrapper, pathKey) {
6034
+ var _a, _b;
6000
6035
  const state = ctx.state;
6001
6036
  const readonly = isElementReadonly(element, state, ctx);
6002
- const initialValue = ctx.prefill[element.key] || element.default || "#000000";
6037
+ const initialValue = (_b = (_a = ctx.prefill[element.key]) != null ? _a : element.default) != null ? _b : "#000000";
6003
6038
  if (readonly) {
6004
6039
  const readonlyUI = createReadonlyColourUI(initialValue);
6005
6040
  wrapper.appendChild(readonlyUI);
6006
6041
  } else {
6007
- const editUI = createEditColourUI(initialValue, pathKey, ctx);
6042
+ const editUI = createEditColourUI(
6043
+ initialValue,
6044
+ pathKey,
6045
+ ctx,
6046
+ element.placeholder
6047
+ );
6008
6048
  wrapper.appendChild(editUI);
6009
6049
  }
6010
6050
  if (!readonly) {
@@ -6019,7 +6059,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
6019
6059
  }
6020
6060
  }
6021
6061
  function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6022
- var _a, _b;
6062
+ var _a, _b, _c;
6023
6063
  const state = ctx.state;
6024
6064
  const readonly = isElementReadonly(element, state, ctx);
6025
6065
  const prefillValues = ctx.prefill[element.key] || [];
@@ -6027,7 +6067,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6027
6067
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
6028
6068
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
6029
6069
  while (values.length < minCount) {
6030
- values.push(element.default || "#000000");
6070
+ values.push((_c = element.default) != null ? _c : "#000000");
6031
6071
  }
6032
6072
  const container = document.createElement("div");
6033
6073
  container.className = "fb-row";
@@ -6051,7 +6091,12 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6051
6091
  }
6052
6092
  } else {
6053
6093
  const tempPathKey = `${pathKey}[${container.children.length}]`;
6054
- const editUI = createEditColourUI(value, tempPathKey, ctx);
6094
+ const editUI = createEditColourUI(
6095
+ value,
6096
+ tempPathKey,
6097
+ ctx,
6098
+ element.placeholder
6099
+ );
6055
6100
  editUI.style.flex = "1";
6056
6101
  itemWrapper.appendChild(editUI);
6057
6102
  }
@@ -6088,6 +6133,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6088
6133
  removeBtn.style.backgroundColor = "transparent";
6089
6134
  });
6090
6135
  removeBtn.onclick = () => {
6136
+ var _a2;
6091
6137
  const currentIndex = Array.from(container.children).indexOf(
6092
6138
  item
6093
6139
  );
@@ -6097,6 +6143,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6097
6143
  updateIndices();
6098
6144
  updateAddButton();
6099
6145
  updateRemoveButtons();
6146
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6100
6147
  }
6101
6148
  };
6102
6149
  item.appendChild(removeBtn);
@@ -6112,11 +6159,13 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6112
6159
  const handle = createAddItemRow(
6113
6160
  "colour",
6114
6161
  () => {
6115
- const defaultColour = element.default || "#000000";
6162
+ var _a2, _b2;
6163
+ const defaultColour = (_a2 = element.default) != null ? _a2 : "#000000";
6116
6164
  values.push(defaultColour);
6117
6165
  addColourItem(defaultColour);
6118
6166
  updateAddButton();
6119
6167
  updateRemoveButtons();
6168
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
6120
6169
  },
6121
6170
  { label: element.addLabel }
6122
6171
  );
@@ -6145,59 +6194,26 @@ function validateColourElement(element, key, context) {
6145
6194
  var _a, _b, _c;
6146
6195
  const errors = [];
6147
6196
  const { scopeRoot, skipValidation } = context;
6148
- const markValidity = (input, errorMessage) => {
6149
- var _a2, _b2;
6150
- if (!input) return;
6151
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
6152
- let errorElement = document.getElementById(errorId);
6153
- if (errorMessage) {
6154
- input.classList.add("invalid");
6155
- input.title = errorMessage;
6156
- if (!errorElement) {
6157
- errorElement = document.createElement("div");
6158
- errorElement.id = errorId;
6159
- errorElement.className = "error-message";
6160
- errorElement.style.cssText = `
6161
- color: var(--fb-error-color);
6162
- font-size: var(--fb-font-size-small);
6163
- margin-top: 0.25rem;
6164
- `;
6165
- if (input.nextSibling) {
6166
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
6167
- } else {
6168
- (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
6169
- }
6170
- }
6171
- errorElement.textContent = errorMessage;
6172
- errorElement.style.display = "block";
6173
- } else {
6174
- input.classList.remove("invalid");
6175
- input.title = "";
6176
- if (errorElement) {
6177
- errorElement.remove();
6178
- }
6179
- }
6180
- };
6181
6197
  const validateColourValue = (input, val, fieldKey) => {
6182
6198
  const { state } = context;
6183
6199
  if (!val) {
6184
6200
  if (!skipValidation && element.required) {
6185
6201
  const msg = t("required", state);
6186
6202
  errors.push(`${fieldKey}: ${msg}`);
6187
- markValidity(input, msg);
6203
+ markFieldValidity(input, msg);
6188
6204
  return "";
6189
6205
  }
6190
- markValidity(input, null);
6206
+ markFieldValidity(input, null);
6191
6207
  return "";
6192
6208
  }
6193
6209
  const normalized = normalizeColourValue(val);
6194
6210
  if (!skipValidation && !isValidHexColour(normalized)) {
6195
6211
  const msg = t("invalidHexColour", state);
6196
6212
  errors.push(`${fieldKey}: ${msg}`);
6197
- markValidity(input, msg);
6213
+ markFieldValidity(input, msg);
6198
6214
  return val;
6199
6215
  }
6200
- markValidity(input, null);
6216
+ markFieldValidity(input, null);
6201
6217
  return normalized;
6202
6218
  };
6203
6219
  if (element.multiple) {
@@ -6235,7 +6251,7 @@ function validateColourElement(element, key, context) {
6235
6251
  if (!skipValidation && element.required && val === "") {
6236
6252
  const msg = t("required", context.state);
6237
6253
  errors.push(`${key}: ${msg}`);
6238
- markValidity(hexInput, msg);
6254
+ markFieldValidity(hexInput, msg);
6239
6255
  return { value: "", errors };
6240
6256
  }
6241
6257
  const validated = validateColourValue(hexInput, val, key);
@@ -6550,6 +6566,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6550
6566
  removeBtn.style.backgroundColor = "transparent";
6551
6567
  });
6552
6568
  removeBtn.onclick = () => {
6569
+ var _a2;
6553
6570
  const currentIndex = Array.from(container.children).indexOf(
6554
6571
  item
6555
6572
  );
@@ -6559,6 +6576,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6559
6576
  updateIndices();
6560
6577
  updateAddButton();
6561
6578
  updateRemoveButtons();
6579
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6562
6580
  }
6563
6581
  };
6564
6582
  item.appendChild(removeBtn);
@@ -6574,10 +6592,12 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
6574
6592
  const handle = createAddItemRow(
6575
6593
  "slider",
6576
6594
  () => {
6595
+ var _a2;
6577
6596
  values.push(defaultValue);
6578
6597
  addSliderItem(defaultValue);
6579
6598
  updateAddButton();
6580
6599
  updateRemoveButtons();
6600
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6581
6601
  },
6582
6602
  { label: element.addLabel }
6583
6603
  );
@@ -6620,43 +6640,6 @@ function validateSliderElement(element, key, context) {
6620
6640
  const max = element.max;
6621
6641
  const step = (_a = element.step) != null ? _a : 1;
6622
6642
  const scale = element.scale || "linear";
6623
- const markValidity = (input, errorMessage) => {
6624
- var _a2, _b2;
6625
- if (!input) return;
6626
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
6627
- let errorElement = document.getElementById(errorId);
6628
- if (errorMessage) {
6629
- input.classList.add("invalid");
6630
- input.title = errorMessage;
6631
- if (!errorElement) {
6632
- errorElement = document.createElement("div");
6633
- errorElement.id = errorId;
6634
- errorElement.className = "error-message";
6635
- errorElement.style.cssText = `
6636
- color: var(--fb-error-color);
6637
- font-size: var(--fb-font-size-small);
6638
- margin-top: 0.25rem;
6639
- `;
6640
- const sliderContainer = input.closest(".slider-container");
6641
- if (sliderContainer && sliderContainer.nextSibling) {
6642
- (_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(
6643
- errorElement,
6644
- sliderContainer.nextSibling
6645
- );
6646
- } else if (sliderContainer) {
6647
- (_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
6648
- }
6649
- }
6650
- errorElement.textContent = errorMessage;
6651
- errorElement.style.display = "block";
6652
- } else {
6653
- input.classList.remove("invalid");
6654
- input.title = "";
6655
- if (errorElement) {
6656
- errorElement.remove();
6657
- }
6658
- }
6659
- };
6660
6643
  const validateSliderValue = (slider, fieldKey) => {
6661
6644
  const { state } = context;
6662
6645
  const rawValue = slider.value;
@@ -6664,10 +6647,10 @@ function validateSliderElement(element, key, context) {
6664
6647
  if (!skipValidation && element.required) {
6665
6648
  const msg = t("required", state);
6666
6649
  errors.push(`${fieldKey}: ${msg}`);
6667
- markValidity(slider, msg);
6650
+ markFieldValidity(slider, msg);
6668
6651
  return null;
6669
6652
  }
6670
- markValidity(slider, null);
6653
+ markFieldValidity(slider, null);
6671
6654
  return null;
6672
6655
  }
6673
6656
  let value;
@@ -6683,17 +6666,17 @@ function validateSliderElement(element, key, context) {
6683
6666
  if (value < min) {
6684
6667
  const msg = t("minValue", state, { min });
6685
6668
  errors.push(`${fieldKey}: ${msg}`);
6686
- markValidity(slider, msg);
6669
+ markFieldValidity(slider, msg);
6687
6670
  return value;
6688
6671
  }
6689
6672
  if (value > max) {
6690
6673
  const msg = t("maxValue", state, { max });
6691
6674
  errors.push(`${fieldKey}: ${msg}`);
6692
- markValidity(slider, msg);
6675
+ markFieldValidity(slider, msg);
6693
6676
  return value;
6694
6677
  }
6695
6678
  }
6696
- markValidity(slider, null);
6679
+ markFieldValidity(slider, null);
6697
6680
  return value;
6698
6681
  };
6699
6682
  if (element.multiple) {
@@ -6839,20 +6822,12 @@ function extractRootFormData(formRoot) {
6839
6822
  inputs.forEach((input) => {
6840
6823
  const fieldName = input.getAttribute("name");
6841
6824
  if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
6842
- if (input instanceof HTMLSelectElement) {
6843
- data[fieldName] = input.value;
6844
- } else if (input instanceof HTMLInputElement) {
6845
- if (input.type === "checkbox") {
6846
- data[fieldName] = input.checked;
6847
- } else if (input.type === "radio") {
6848
- if (input.checked) {
6849
- data[fieldName] = input.value;
6850
- }
6851
- } else {
6825
+ if (input instanceof HTMLInputElement && input.type === "radio") {
6826
+ if (input.checked) {
6852
6827
  data[fieldName] = input.value;
6853
6828
  }
6854
- } else if (input instanceof HTMLTextAreaElement) {
6855
- data[fieldName] = input.value;
6829
+ } else {
6830
+ data[fieldName] = readTypedInputValue(input);
6856
6831
  }
6857
6832
  }
6858
6833
  });
@@ -6921,9 +6896,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
6921
6896
  inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
6922
6897
  };
6923
6898
  element.elements.forEach((child) => {
6924
- var _a2, _b2;
6899
+ var _a2;
6925
6900
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
6926
- const prefillVal = (_b2 = (_a2 = containerPrefill[child.key]) != null ? _a2 : "default" in child ? child.default : null) != null ? _b2 : null;
6901
+ const prefillVal = child.key in containerPrefill ? containerPrefill[child.key] : (_a2 = "default" in child ? child.default : null) != null ? _a2 : null;
6927
6902
  itemsWrap.appendChild(
6928
6903
  createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
6929
6904
  );
@@ -6968,7 +6943,7 @@ function mountRemoveButton(item, onRemove, state, containerLabel) {
6968
6943
  item.classList.add("fb-row-removable");
6969
6944
  item.insertBefore(rem, item.firstChild);
6970
6945
  }
6971
- function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6946
+ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
6972
6947
  var _a, _b, _c, _d;
6973
6948
  const state = ctx.state;
6974
6949
  const containerIsReadonly = isElementReadonly(element, state, ctx);
@@ -6999,9 +6974,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6999
6974
  );
7000
6975
  const countItems = () => directRows().length;
7001
6976
  const handleRemoveItem = (item) => {
6977
+ var _a2;
7002
6978
  if (countItems() <= min) return;
7003
6979
  item.remove();
7004
6980
  updateAddButton();
6981
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
7005
6982
  };
7006
6983
  const createContainerItem = (idx, rowPrefill, formData) => {
7007
6984
  const subCtx = {
@@ -7023,9 +7000,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7023
7000
  isSlides ? void 0 : element.columns
7024
7001
  );
7025
7002
  element.elements.forEach((child) => {
7026
- var _a2, _b2;
7003
+ var _a2;
7027
7004
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
7028
- const hiddenValue = (_b2 = (_a2 = rowPrefill == null ? void 0 : rowPrefill[child.key]) != null ? _a2 : "default" in child ? child.default : null) != null ? _b2 : null;
7005
+ const hiddenValue = rowPrefill && child.key in rowPrefill ? rowPrefill[child.key] : (_a2 = "default" in child ? child.default : null) != null ? _a2 : null;
7029
7006
  childWrapper.appendChild(
7030
7007
  createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
7031
7008
  );
@@ -7045,6 +7022,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7045
7022
  return item;
7046
7023
  };
7047
7024
  const handleAddItem = () => {
7025
+ var _a2;
7048
7026
  if (countItems() >= max) return;
7049
7027
  const item = createContainerItem(
7050
7028
  takeRowIndex(),
@@ -7059,6 +7037,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7059
7037
  itemsWrap.appendChild(item);
7060
7038
  }
7061
7039
  updateAddButton();
7040
+ (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
7062
7041
  };
7063
7042
  let slideAddTile = null;
7064
7043
  let slideAddUpdate = null;
@@ -7134,19 +7113,16 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7134
7113
  }
7135
7114
  }
7136
7115
  }
7137
- var validateElementFunc = null;
7138
- function setValidateElement(fn) {
7139
- validateElementFunc = fn;
7140
- }
7141
- function validateElement(element, ctx, customScopeRoot) {
7142
- if (!validateElementFunc) {
7116
+ function requireValidateElement(context) {
7117
+ if (!context.validateElement) {
7143
7118
  throw new Error(
7144
- "validateElement not initialized. Should be set from FormBuilderInstance"
7119
+ "validateContainerElement: context.validateElement missing \u2014 container validation requires the instance validator"
7145
7120
  );
7146
7121
  }
7147
- return validateElementFunc(element, ctx, customScopeRoot);
7122
+ return context.validateElement;
7148
7123
  }
7149
7124
  function validateContainerElement(element, key, context) {
7125
+ const validateChild = requireValidateElement(context);
7150
7126
  const errors = [];
7151
7127
  const { scopeRoot, skipValidation, path } = context;
7152
7128
  if (!("elements" in element)) {
@@ -7199,7 +7175,7 @@ function validateContainerElement(element, key, context) {
7199
7175
  }
7200
7176
  }
7201
7177
  const childKey = `${key}[${domIndex}].${child.key}`;
7202
- const childResult = validateElement(
7178
+ const childResult = validateChild(
7203
7179
  { ...child, key: childKey },
7204
7180
  { path },
7205
7181
  itemContainer
@@ -7241,7 +7217,7 @@ function validateContainerElement(element, key, context) {
7241
7217
  }
7242
7218
  {
7243
7219
  const childKey = `${key}.${child.key}`;
7244
- const childResult = validateElement(
7220
+ const childResult = validateChild(
7245
7221
  { ...child, key: childKey },
7246
7222
  { path },
7247
7223
  containerContainer
@@ -7360,7 +7336,7 @@ function renderGroupElement(element, ctx, wrapper, pathKey) {
7360
7336
  maxCount: (_b = element.repeat) == null ? void 0 : _b.max
7361
7337
  };
7362
7338
  if (containerElement.multiple) {
7363
- renderMultipleContainerElement(containerElement, ctx, wrapper);
7339
+ renderMultipleContainerElement(containerElement, ctx, wrapper, pathKey);
7364
7340
  } else {
7365
7341
  renderSingleContainerElement(containerElement, ctx, wrapper, pathKey);
7366
7342
  }
@@ -7744,7 +7720,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
7744
7720
  rebuild();
7745
7721
  } catch (e) {
7746
7722
  const errMsg = e instanceof Error ? e.message : String(e);
7747
- console.error(t("tableImportError", state).replace("{error}", errMsg));
7723
+ console.error(t("tableImportError", state, { error: errMsg }));
7748
7724
  } finally {
7749
7725
  overlay.remove();
7750
7726
  }
@@ -8704,7 +8680,7 @@ function updateTableField(element, fieldPath, value, context) {
8704
8680
  }
8705
8681
 
8706
8682
  // src/components/richinput.ts
8707
- function applyAutoExpand2(textarea, backdrop) {
8683
+ function applyAutoExpand2(textarea, backdrop, observers) {
8708
8684
  textarea.style.overflow = "hidden";
8709
8685
  textarea.style.resize = "none";
8710
8686
  const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
@@ -8727,6 +8703,7 @@ function applyAutoExpand2(textarea, backdrop) {
8727
8703
  var _a, _b, _c, _d, _e;
8728
8704
  if (!textarea.isConnected) {
8729
8705
  ro.disconnect();
8706
+ observers.delete(ro);
8730
8707
  return;
8731
8708
  }
8732
8709
  const entry = entries[0];
@@ -8736,6 +8713,7 @@ function applyAutoExpand2(textarea, backdrop) {
8736
8713
  resize();
8737
8714
  });
8738
8715
  ro.observe(textarea);
8716
+ observers.add(ro);
8739
8717
  }
8740
8718
  function buildFileLabels(files, state) {
8741
8719
  var _a, _b, _c, _d;
@@ -9164,7 +9142,7 @@ function filterFilesForDropdown(query, files, labels) {
9164
9142
  var TEXTAREA_FONT = "font-size: var(--fb-font-size, 14px); font-family: var(--fb-font-family, inherit); line-height: 1.6;";
9165
9143
  var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
9166
9144
  function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9167
- var _a;
9145
+ var _a, _b;
9168
9146
  const state = ctx.state;
9169
9147
  const files = [...initialValue.files];
9170
9148
  const dropdownState = {
@@ -9178,12 +9156,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9178
9156
  hiddenInput.type = "hidden";
9179
9157
  hiddenInput.name = pathKey;
9180
9158
  function getCurrentValue() {
9181
- var _a2, _b;
9159
+ var _a2, _b2;
9182
9160
  const rawText = textarea.value;
9183
9161
  const nameToRid = buildNameToRid(files, state);
9184
9162
  const submissionText = rawText ? replaceFilenamesWithRids(rawText, nameToRid) : null;
9185
9163
  const textKey = (_a2 = element.textKey) != null ? _a2 : "text";
9186
- const filesKey = (_b = element.filesKey) != null ? _b : "files";
9164
+ const filesKey = (_b2 = element.filesKey) != null ? _b2 : "files";
9187
9165
  return {
9188
9166
  [textKey]: rawText === "" ? null : submissionText,
9189
9167
  [filesKey]: [...files]
@@ -9265,14 +9243,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9265
9243
  }
9266
9244
  });
9267
9245
  outerDiv.addEventListener("drop", (e) => {
9268
- var _a2, _b;
9246
+ var _a2, _b2;
9269
9247
  e.preventDefault();
9270
9248
  dragCounter = 0;
9271
9249
  outerDiv.style.borderColor = "var(--fb-border-color, #d1d5db)";
9272
9250
  outerDiv.style.boxShadow = "none";
9273
9251
  const droppedFiles = (_a2 = e.dataTransfer) == null ? void 0 : _a2.files;
9274
9252
  if (!droppedFiles || !state.config.uploadFile) return;
9275
- const maxFiles = (_b = element.maxFiles) != null ? _b : Infinity;
9253
+ const maxFiles = (_b2 = element.maxFiles) != null ? _b2 : Infinity;
9276
9254
  for (let i = 0; i < droppedFiles.length; i++) {
9277
9255
  if (files.length >= maxFiles) {
9278
9256
  showUploadError(
@@ -9321,8 +9299,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9321
9299
  `;
9322
9300
  const textarea = document.createElement("textarea");
9323
9301
  textarea.name = `${pathKey}__text`;
9324
- textarea.placeholder = element.placeholder || t("richinputPlaceholder", state);
9325
- const rawInitialText = (_a = initialValue.text) != null ? _a : "";
9302
+ textarea.placeholder = (_a = element.placeholder) != null ? _a : t("richinputPlaceholder", state);
9303
+ const rawInitialText = (_b = initialValue.text) != null ? _b : "";
9326
9304
  textarea.value = rawInitialText ? replaceRidsWithFilenames(rawInitialText, files, state) : "";
9327
9305
  textarea.style.cssText = `
9328
9306
  width: 100%;
@@ -9338,14 +9316,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9338
9316
  z-index: 1;
9339
9317
  caret-color: var(--fb-text-color, #111827);
9340
9318
  `;
9341
- applyAutoExpand2(textarea, backdrop);
9319
+ applyAutoExpand2(textarea, backdrop, ctx.state.autoExpandObservers);
9342
9320
  textarea.addEventListener("scroll", () => {
9343
9321
  backdrop.scrollTop = textarea.scrollTop;
9344
9322
  });
9345
9323
  let mentionTooltip = null;
9346
9324
  backdrop.addEventListener("mouseover", (e) => {
9347
- var _a2, _b;
9348
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
9325
+ var _a2, _b2;
9326
+ const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
9349
9327
  _a2,
9350
9328
  "mark"
9351
9329
  );
@@ -9354,8 +9332,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9354
9332
  mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
9355
9333
  });
9356
9334
  backdrop.addEventListener("mouseout", (e) => {
9357
- var _a2, _b, _c;
9358
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
9335
+ var _a2, _b2, _c;
9336
+ const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
9359
9337
  _a2,
9360
9338
  "mark"
9361
9339
  );
@@ -9365,8 +9343,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9365
9343
  mentionTooltip = removePortalTooltip(mentionTooltip);
9366
9344
  });
9367
9345
  backdrop.addEventListener("mousedown", (e) => {
9368
- var _a2, _b;
9369
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
9346
+ var _a2, _b2;
9347
+ const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
9370
9348
  _a2,
9371
9349
  "mark"
9372
9350
  );
@@ -9539,8 +9517,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9539
9517
  dropdown.appendChild(item);
9540
9518
  });
9541
9519
  dropdown.onmousemove = (e) => {
9542
- var _a2, _b, _c;
9543
- const target = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
9520
+ var _a2, _b2, _c;
9521
+ const target = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
9544
9522
  _a2,
9545
9523
  ".fb-richinput-dropdown-item"
9546
9524
  );
@@ -9556,10 +9534,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9556
9534
  dropdownState.selectedIndex = newIdx;
9557
9535
  };
9558
9536
  dropdown.onmousedown = (e) => {
9559
- var _a2, _b;
9537
+ var _a2, _b2;
9560
9538
  e.preventDefault();
9561
9539
  e.stopPropagation();
9562
- const target = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
9540
+ const target = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
9563
9541
  _a2,
9564
9542
  ".fb-richinput-dropdown-item"
9565
9543
  );
@@ -9587,9 +9565,9 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9587
9565
  dropdownState.open = false;
9588
9566
  }
9589
9567
  function insertMention(rid) {
9590
- var _a2, _b, _c, _d;
9568
+ var _a2, _b2, _c, _d;
9591
9569
  const labels = buildFileLabelsFromClosure();
9592
- const label = (_c = (_b = labels.get(rid)) != null ? _b : (_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.name) != null ? _c : rid;
9570
+ const label = (_c = (_b2 = labels.get(rid)) != null ? _b2 : (_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.name) != null ? _c : rid;
9593
9571
  const cursorPos = (_d = textarea.selectionStart) != null ? _d : 0;
9594
9572
  const before = textarea.value.slice(0, dropdownState.triggerPos);
9595
9573
  const after = textarea.value.slice(cursorPos);
@@ -9681,10 +9659,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9681
9659
  thumbWrapper.appendChild(thumbInner);
9682
9660
  const tooltipHandle = createTooltipHandle();
9683
9661
  const doMention = () => {
9684
- var _a2, _b, _c;
9662
+ var _a2, _b2, _c;
9685
9663
  const cursorPos = (_a2 = textarea.selectionStart) != null ? _a2 : textarea.value.length;
9686
9664
  const labels = buildFileLabelsFromClosure();
9687
- const label = (_c = (_b = labels.get(rid)) != null ? _b : meta == null ? void 0 : meta.name) != null ? _c : rid;
9665
+ const label = (_c = (_b2 = labels.get(rid)) != null ? _b2 : meta == null ? void 0 : meta.name) != null ? _c : rid;
9688
9666
  const before = textarea.value.slice(0, cursorPos);
9689
9667
  const after = textarea.value.slice(cursorPos);
9690
9668
  const prefix = before.length > 0 && !/[\s\n]$/.test(before) ? "\n" : "";
@@ -9762,12 +9740,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9762
9740
  writeHidden();
9763
9741
  (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey, getCurrentValue());
9764
9742
  }).catch((err) => {
9765
- var _a2, _b;
9743
+ var _a2, _b2;
9766
9744
  const idx = files.indexOf(tempId);
9767
9745
  if (idx !== -1) files.splice(idx, 1);
9768
9746
  state.resourceIndex.delete(tempId);
9769
9747
  renderFilesRow();
9770
- (_b = (_a2 = state.config).onUploadError) == null ? void 0 : _b.call(_a2, err, file);
9748
+ (_b2 = (_a2 = state.config).onUploadError) == null ? void 0 : _b2.call(_a2, err, file);
9771
9749
  });
9772
9750
  }
9773
9751
  fileInput.addEventListener("change", () => {
@@ -10339,12 +10317,7 @@ function validateHiddenElement(element, key, context) {
10339
10317
  const input = scopeRoot.querySelector(
10340
10318
  `input[type="hidden"][data-hidden-field="true"][name="${key}"]`
10341
10319
  );
10342
- const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
10343
- if (raw === "") {
10344
- const defaultVal = "default" in element ? element.default : null;
10345
- return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
10346
- }
10347
- return { value: deserializeHiddenValue(raw), errors: [] };
10320
+ return { value: deserializeHiddenValue((_a = input == null ? void 0 : input.value) != null ? _a : ""), errors: [] };
10348
10321
  }
10349
10322
  function updateHiddenField(_element, fieldPath, value, context) {
10350
10323
  const { scopeRoot } = context;
@@ -10428,15 +10401,19 @@ var componentRegistry = {
10428
10401
  function getComponentOperations(elementType) {
10429
10402
  return componentRegistry[elementType] || null;
10430
10403
  }
10404
+ function resolveOperations(element) {
10405
+ const isHiddenField = element.type !== "markdown" && (element.type === "hidden" || Boolean(element.hidden));
10406
+ return isHiddenField ? componentRegistry.hidden : getComponentOperations(element.type);
10407
+ }
10431
10408
  function validateElementWithComponent(element, key, context) {
10432
- const ops = getComponentOperations(element.type);
10409
+ const ops = resolveOperations(element);
10433
10410
  if (ops && ops.validate) {
10434
10411
  return ops.validate(element, key, context);
10435
10412
  }
10436
10413
  return null;
10437
10414
  }
10438
10415
  function updateElementWithComponent(element, fieldPath, value, context) {
10439
- const ops = getComponentOperations(element.type);
10416
+ const ops = resolveOperations(element);
10440
10417
  if (ops && ops.update) {
10441
10418
  ops.update(element, fieldPath, value, context);
10442
10419
  return true;
@@ -10448,6 +10425,10 @@ function updateElementWithComponent(element, fieldPath, value, context) {
10448
10425
  function showTooltip(tooltipId, button) {
10449
10426
  const tooltip = document.getElementById(tooltipId);
10450
10427
  if (!tooltip) return;
10428
+ if (!button.isConnected) {
10429
+ tooltip.remove();
10430
+ return;
10431
+ }
10451
10432
  const isCurrentlyVisible = !tooltip.classList.contains("hidden");
10452
10433
  document.querySelectorAll('[id^="tooltip-"]').forEach((t2) => {
10453
10434
  t2.classList.add("hidden");
@@ -10528,23 +10509,13 @@ function extractDOMValue(fieldPath, formRoot) {
10528
10509
  if (!input) {
10529
10510
  return void 0;
10530
10511
  }
10531
- if (input instanceof HTMLSelectElement) {
10532
- return input.value;
10533
- } else if (input instanceof HTMLInputElement) {
10534
- if (input.type === "checkbox") {
10535
- return input.checked;
10536
- } else if (input.type === "radio") {
10537
- const checked = formRoot.querySelector(
10538
- `[name="${fieldPath}"]:checked`
10539
- );
10540
- return checked ? checked.value : void 0;
10541
- } else {
10542
- return input.value;
10543
- }
10544
- } else if (input instanceof HTMLTextAreaElement) {
10545
- return input.value;
10512
+ if (input instanceof HTMLInputElement && input.type === "radio") {
10513
+ const checked = formRoot.querySelector(
10514
+ `[name="${fieldPath}"]:checked`
10515
+ );
10516
+ return checked ? checked.value : void 0;
10546
10517
  }
10547
- return void 0;
10518
+ return readTypedInputValue(input);
10548
10519
  }
10549
10520
  function buildScopedDataAtPath(path, value) {
10550
10521
  const segments = path.match(/[^.[\]]+|\[\d+\]/g);
@@ -10676,11 +10647,19 @@ function createFieldLabel(element) {
10676
10647
  }
10677
10648
  return title;
10678
10649
  }
10679
- function createInfoButton(element) {
10650
+ function ensureTooltipStyles(doc) {
10651
+ if (doc.head.querySelector("[data-fb-tooltip-styles]")) return;
10652
+ const style = doc.createElement("style");
10653
+ style.setAttribute("data-fb-tooltip-styles", "");
10654
+ style.textContent = `[id^="tooltip-"].hidden { display: none; }`;
10655
+ doc.head.appendChild(style);
10656
+ }
10657
+ function createInfoButton(element, state) {
10680
10658
  const infoBtn = document.createElement("button");
10681
10659
  infoBtn.type = "button";
10682
10660
  infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
10683
10661
  infoBtn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
10662
+ ensureTooltipStyles(document);
10684
10663
  const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
10685
10664
  const tooltip = document.createElement("div");
10686
10665
  tooltip.id = tooltipId;
@@ -10688,6 +10667,7 @@ function createInfoButton(element) {
10688
10667
  tooltip.style.position = "fixed";
10689
10668
  tooltip.textContent = element.description || element.hint || "Field information";
10690
10669
  document.body.appendChild(tooltip);
10670
+ state.tooltipElements.add(tooltip);
10691
10671
  infoBtn.onclick = (e) => {
10692
10672
  e.preventDefault();
10693
10673
  e.stopPropagation();
@@ -10695,7 +10675,7 @@ function createInfoButton(element) {
10695
10675
  };
10696
10676
  return infoBtn;
10697
10677
  }
10698
- function createLabelContainer(element) {
10678
+ function createLabelContainer(element, state) {
10699
10679
  const label = document.createElement("div");
10700
10680
  label.className = "flex items-center";
10701
10681
  label.style.marginBottom = "var(--fb-label-margin-bottom, 2px)";
@@ -10703,7 +10683,7 @@ function createLabelContainer(element) {
10703
10683
  const title = createFieldLabel(element);
10704
10684
  label.appendChild(title);
10705
10685
  if (element.description || element.hint) {
10706
- const infoBtn = createInfoButton(element);
10686
+ const infoBtn = createInfoButton(element, state);
10707
10687
  label.appendChild(infoBtn);
10708
10688
  }
10709
10689
  return label;
@@ -10778,7 +10758,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
10778
10758
  break;
10779
10759
  case "container":
10780
10760
  if (isMultiple) {
10781
- renderMultipleContainerElement(element, ctx, wrapper);
10761
+ renderMultipleContainerElement(element, ctx, wrapper, pathKey);
10782
10762
  } else {
10783
10763
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
10784
10764
  }
@@ -10831,7 +10811,7 @@ function renderElement2(element, ctx) {
10831
10811
  wrapper.setAttribute("data-fb-width", element.width || "full");
10832
10812
  const ops = getComponentOperations(element.type);
10833
10813
  if (!(ops == null ? void 0 : ops.ownsLabel)) {
10834
- const label = createLabelContainer(element);
10814
+ const label = createLabelContainer(element, ctx.state);
10835
10815
  wrapper.appendChild(label);
10836
10816
  }
10837
10817
  const pathKey = pathJoin(ctx.path, element.key);
@@ -10860,6 +10840,7 @@ var defaultConfig = {
10860
10840
  onDownloadError: null,
10861
10841
  debounceMs: 300,
10862
10842
  verboseErrors: false,
10843
+ postMessageTarget: null,
10863
10844
  enableFilePreview: true,
10864
10845
  maxPreviewSize: "200px",
10865
10846
  readonly: false,
@@ -10881,6 +10862,7 @@ var defaultConfig = {
10881
10862
  openInNewTab: "Open in new tab",
10882
10863
  changeButton: "Change",
10883
10864
  placeholderText: "Enter text",
10865
+ selectPlaceholder: "Select\u2026",
10884
10866
  previewAlt: "Preview",
10885
10867
  previewUnavailable: "Preview unavailable",
10886
10868
  previewError: "Preview error",
@@ -10956,6 +10938,7 @@ var defaultConfig = {
10956
10938
  openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
10957
10939
  changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
10958
10940
  placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
10941
+ selectPlaceholder: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435\u2026",
10959
10942
  previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
10960
10943
  previewUnavailable: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
10961
10944
  previewError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430",
@@ -11020,20 +11003,23 @@ var defaultConfig = {
11020
11003
  },
11021
11004
  theme: {}
11022
11005
  };
11023
- function createInstanceState(config) {
11024
- const mergedTranslations = {
11025
- ...defaultConfig.translations
11026
- };
11027
- if (config == null ? void 0 : config.translations) {
11028
- for (const [locale, userTranslations] of Object.entries(
11029
- config.translations
11030
- )) {
11031
- mergedTranslations[locale] = {
11032
- ...defaultConfig.translations[locale] || {},
11006
+ function mergeTranslations(base, overrides) {
11007
+ const merged = { ...base };
11008
+ if (overrides) {
11009
+ for (const [locale, userTranslations] of Object.entries(overrides)) {
11010
+ merged[locale] = {
11011
+ ...base[locale] || {},
11033
11012
  ...userTranslations
11034
11013
  };
11035
11014
  }
11036
11015
  }
11016
+ return merged;
11017
+ }
11018
+ function createInstanceState(config) {
11019
+ const mergedTranslations = mergeTranslations(
11020
+ defaultConfig.translations,
11021
+ config == null ? void 0 : config.translations
11022
+ );
11037
11023
  return {
11038
11024
  schema: null,
11039
11025
  formRoot: null,
@@ -11049,7 +11035,9 @@ function createInstanceState(config) {
11049
11035
  prefill: {},
11050
11036
  syntheticElementIds: /* @__PURE__ */ new WeakMap(),
11051
11037
  syntheticElementIdCounter: 0,
11052
- enableIfObservers: /* @__PURE__ */ new Set()
11038
+ enableIfObservers: /* @__PURE__ */ new Set(),
11039
+ autoExpandObservers: /* @__PURE__ */ new Set(),
11040
+ tooltipElements: /* @__PURE__ */ new Set()
11053
11041
  };
11054
11042
  }
11055
11043
  function generateInstanceId() {
@@ -11350,6 +11338,11 @@ function findOwnField(scope, lookupKey, ownBoundary) {
11350
11338
  }
11351
11339
  var FormBuilderInstance = class {
11352
11340
  constructor(config) {
11341
+ // The bound prefill-hint click handler currently attached to the form root.
11342
+ // Kept so renderForm()/destroy() can remove it — re-binding on every render
11343
+ // stacked listeners (hint clicks applied values N times) and destroy()
11344
+ // left the last one on the host-owned root, retaining the instance.
11345
+ this.prefillHintHandler = null;
11353
11346
  this.instanceId = generateInstanceId();
11354
11347
  this.state = createInstanceState(config);
11355
11348
  if (this.state.config.verboseErrors) {
@@ -11383,10 +11376,21 @@ var FormBuilderInstance = class {
11383
11376
  this.state.formRoot = element;
11384
11377
  }
11385
11378
  /**
11386
- * Configure the form builder
11379
+ * Configure the form builder. Translations deep-merge per locale (same as
11380
+ * the constructor); a locale without translations — configured or default —
11381
+ * is rejected, matching setLocale.
11387
11382
  */
11388
11383
  configure(config) {
11389
- Object.assign(this.state.config, config);
11384
+ const translations = mergeTranslations(
11385
+ this.state.config.translations,
11386
+ config.translations
11387
+ );
11388
+ if (config.locale !== void 0 && !translations[config.locale]) {
11389
+ throw new Error(
11390
+ `configure: no translations configured for locale "${config.locale}"`
11391
+ );
11392
+ }
11393
+ Object.assign(this.state.config, config, { translations });
11390
11394
  }
11391
11395
  /**
11392
11396
  * Set file upload handler
@@ -11419,17 +11423,25 @@ var FormBuilderInstance = class {
11419
11423
  this.state.config.readonly = mode === "readonly";
11420
11424
  }
11421
11425
  /**
11422
- * Set locale
11426
+ * Set locale. Custom locales are allowed — their translations must have
11427
+ * been provided via the constructor or configure() first.
11423
11428
  */
11424
11429
  setLocale(locale) {
11425
- if (this.state.config.translations[locale]) {
11426
- this.state.config.locale = locale;
11430
+ if (!this.state.config.translations[locale]) {
11431
+ throw new Error(
11432
+ `setLocale: no translations configured for locale "${locale}"`
11433
+ );
11427
11434
  }
11435
+ this.state.config.locale = locale;
11428
11436
  }
11429
11437
  /**
11430
11438
  * Trigger onChange callbacks with debouncing
11431
11439
  * @param fieldPath - Optional field path for field-specific change events
11432
- * @param fieldValue - Optional field value for field-specific change events
11440
+ * @param fieldValue - Optional field value for field-specific change events.
11441
+ * When omitted while fieldPath is given, the value is read from the
11442
+ * freshly extracted form data at debounce time — used by structural
11443
+ * changes (multi-item add/remove), where the handler has no cheap
11444
+ * current value but the array is trivially derivable after the fact.
11433
11445
  */
11434
11446
  triggerOnChange(fieldPath, fieldValue) {
11435
11447
  if (this.state.config.readonly) return;
@@ -11442,12 +11454,49 @@ var FormBuilderInstance = class {
11442
11454
  if (this.state.config.onChange) {
11443
11455
  this.state.config.onChange(formData);
11444
11456
  }
11445
- if (this.state.config.onFieldChange && fieldPath !== void 0 && fieldValue !== void 0) {
11446
- this.state.config.onFieldChange(fieldPath, fieldValue, formData);
11457
+ if (this.state.config.onFieldChange && fieldPath !== void 0) {
11458
+ const resolvedValue = fieldValue !== void 0 ? fieldValue : this.resolveDomPathValue(formData.data, fieldPath);
11459
+ this.state.config.onFieldChange(fieldPath, resolvedValue, formData);
11447
11460
  }
11448
11461
  this.state.debounceTimer = null;
11449
11462
  }, this.state.config.debounceMs);
11450
11463
  }
11464
+ /**
11465
+ * Resolve a DOM field path against the extracted form data.
11466
+ *
11467
+ * A plain getValueByPath is wrong for paths inside a multiple container:
11468
+ * row markers keep gaps after a deletion (`s[2]` may be the first surviving
11469
+ * row) while the extracted array is re-packed contiguously — the naive
11470
+ * lookup would read a different row, or nothing. Each `[N]` segment is
11471
+ * mapped from its marker to the row's position among the container's
11472
+ * rendered rows, the same DOM order extraction used to build the array.
11473
+ * A bracketed segment that is not a container marker (a multi-value leaf
11474
+ * like `tags[1]`, whose indices are contiguous) falls back to the index.
11475
+ */
11476
+ resolveDomPathValue(data, domPath) {
11477
+ let cur = data;
11478
+ let domPrefix = "";
11479
+ for (const seg of domPath.split(".")) {
11480
+ if (cur === null || cur === void 0) return void 0;
11481
+ const marker = seg.match(/^(.+)\[(\d+)\]$/);
11482
+ if (!marker) {
11483
+ domPrefix = domPrefix ? `${domPrefix}.${seg}` : seg;
11484
+ cur = cur[seg];
11485
+ continue;
11486
+ }
11487
+ const key = marker[1];
11488
+ domPrefix = domPrefix ? `${domPrefix}.${key}` : key;
11489
+ const arr = cur[key];
11490
+ if (!Array.isArray(arr)) return void 0;
11491
+ const rows = this.state.formRoot ? findDirectContainerRows(this.state.formRoot, domPrefix) : [];
11492
+ domPrefix = `${domPrefix}[${marker[2]}]`;
11493
+ const pos = rows.findIndex(
11494
+ (row) => row.getAttribute("data-container-item") === domPrefix
11495
+ );
11496
+ cur = pos >= 0 ? arr[pos] : arr[parseInt(marker[2], 10)];
11497
+ }
11498
+ return cur;
11499
+ }
11451
11500
  /**
11452
11501
  * Register an external action that will be displayed as a button
11453
11502
  * External actions can be form-level (no related_field) or field-level (with related_field)
@@ -11487,21 +11536,10 @@ var FormBuilderInstance = class {
11487
11536
  */
11488
11537
  findFormElementByFieldPath(fieldPath) {
11489
11538
  if (!this.state.formRoot) return null;
11490
- let element = this.state.formRoot.querySelector(
11539
+ const element = this.state.formRoot.querySelector(
11491
11540
  `[name="${fieldPath}"]`
11492
11541
  );
11493
11542
  if (element) return element;
11494
- const variations = [
11495
- fieldPath,
11496
- fieldPath.replace(/\[(\d+)\]/g, "[$1]"),
11497
- fieldPath.replace(/\./g, "[") + "]".repeat((fieldPath.match(/\./g) || []).length)
11498
- ];
11499
- for (const variation of variations) {
11500
- element = this.state.formRoot.querySelector(
11501
- `[name="${variation}"]`
11502
- );
11503
- if (element) return element;
11504
- }
11505
11543
  const schemaElement = this.findSchemaElement(fieldPath);
11506
11544
  if (!schemaElement) return null;
11507
11545
  const fieldWrappers = this.state.formRoot.querySelectorAll(".fb-field-wrapper");
@@ -11747,10 +11785,13 @@ var FormBuilderInstance = class {
11747
11785
  renderForm(root, schema, prefill, actions) {
11748
11786
  const errors = validateSchema(schema);
11749
11787
  if (errors.length > 0) {
11750
- console.error("Schema validation errors:", errors);
11751
- return;
11788
+ throw new Error(`renderForm: invalid schema:
11789
+ - ${errors.join("\n- ")}`);
11752
11790
  }
11753
11791
  this.disconnectEnableIfObservers();
11792
+ this.disconnectAutoExpandObservers();
11793
+ this.removeTooltipElements();
11794
+ this.removePrefillHintListener();
11754
11795
  this.state.formRoot = root;
11755
11796
  this.state.schema = schema;
11756
11797
  this.state.externalActions = actions || null;
@@ -11772,9 +11813,9 @@ var FormBuilderInstance = class {
11772
11813
  fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
11773
11814
  }
11774
11815
  schema.elements.forEach((element) => {
11775
- var _a, _b;
11816
+ var _a;
11776
11817
  if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
11777
- const val = (_b = (_a = prefill == null ? void 0 : prefill[element.key]) != null ? _a : element.default) != null ? _b : null;
11818
+ const val = prefill && element.key in prefill ? prefill[element.key] : (_a = element.default) != null ? _a : null;
11778
11819
  fieldsWrapper.appendChild(createHiddenInput(element.key, val));
11779
11820
  return;
11780
11821
  }
@@ -11791,7 +11832,10 @@ var FormBuilderInstance = class {
11791
11832
  rootContainer.appendChild(fieldsWrapper);
11792
11833
  root.appendChild(rootContainer);
11793
11834
  if (!this.state.config.readonly) {
11794
- root.addEventListener("click", this.handlePrefillHintClick.bind(this));
11835
+ this.prefillHintHandler = this.handlePrefillHintClick.bind(
11836
+ this
11837
+ );
11838
+ root.addEventListener("click", this.prefillHintHandler);
11795
11839
  }
11796
11840
  if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
11797
11841
  this.renderExternalActions();
@@ -11807,7 +11851,7 @@ var FormBuilderInstance = class {
11807
11851
  return { valid: true, errors: [], data: {} };
11808
11852
  const errors = [];
11809
11853
  const data = {};
11810
- const validateElement2 = (element, ctx, customScopeRoot = null) => {
11854
+ const validateElement = (element, ctx, customScopeRoot = null) => {
11811
11855
  var _a;
11812
11856
  const key = (_a = element.key) != null ? _a : "";
11813
11857
  const scopeRoot = customScopeRoot || this.state.formRoot;
@@ -11816,7 +11860,10 @@ var FormBuilderInstance = class {
11816
11860
  state: this.state,
11817
11861
  instance: this,
11818
11862
  path: ctx.path,
11819
- skipValidation
11863
+ skipValidation,
11864
+ // Containers recurse into their children through this — threaded per
11865
+ // pass, never module state (see ComponentContext.validateElement).
11866
+ validateElement
11820
11867
  };
11821
11868
  const componentResult = validateElementWithComponent(
11822
11869
  element,
@@ -11834,9 +11881,7 @@ var FormBuilderInstance = class {
11834
11881
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
11835
11882
  return { value: null, spread: false };
11836
11883
  };
11837
- setValidateElement(validateElement2);
11838
11884
  this.state.schema.elements.forEach((element) => {
11839
- var _a;
11840
11885
  if (element.enableIf) {
11841
11886
  try {
11842
11887
  const shouldEnable = evaluateEnableCondition(element.enableIf, data);
@@ -11853,24 +11898,12 @@ var FormBuilderInstance = class {
11853
11898
  if (element.type === "markdown") {
11854
11899
  return;
11855
11900
  }
11856
- if (element.hidden || element.type === "hidden") {
11857
- const hiddenInput = this.state.formRoot.querySelector(
11858
- `input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
11859
- );
11860
- const raw = (_a = hiddenInput == null ? void 0 : hiddenInput.value) != null ? _a : "";
11861
- if (raw !== "") {
11862
- data[element.key] = deserializeHiddenValue(raw);
11863
- } else {
11864
- data[element.key] = element.default !== void 0 ? element.default : null;
11865
- }
11866
- } else {
11867
- const result = validateElement2(element, { path: "" });
11868
- if (result.skip) return;
11869
- if (result.spread && result.value !== null && typeof result.value === "object") {
11870
- Object.assign(data, result.value);
11871
- } else if (element.key) {
11872
- data[element.key] = result.value;
11873
- }
11901
+ const result = validateElement(element, { path: "" });
11902
+ if (result.skip) return;
11903
+ if (result.spread && result.value !== null && typeof result.value === "object") {
11904
+ Object.assign(data, result.value);
11905
+ } else if (element.key) {
11906
+ data[element.key] = result.value;
11874
11907
  }
11875
11908
  });
11876
11909
  return {
@@ -11891,16 +11924,7 @@ var FormBuilderInstance = class {
11891
11924
  submitForm() {
11892
11925
  const result = this.validateForm(false);
11893
11926
  if (result.valid) {
11894
- if (typeof window !== "undefined" && window.parent) {
11895
- window.parent.postMessage(
11896
- {
11897
- type: "formSubmit",
11898
- data: result.data,
11899
- schema: this.state.schema
11900
- },
11901
- "*"
11902
- );
11903
- }
11927
+ this.postToParent("formSubmit", result.data);
11904
11928
  }
11905
11929
  return result;
11906
11930
  }
@@ -11909,17 +11933,27 @@ var FormBuilderInstance = class {
11909
11933
  */
11910
11934
  saveDraft() {
11911
11935
  const result = this.validateForm(true);
11912
- if (typeof window !== "undefined" && window.parent) {
11913
- window.parent.postMessage(
11914
- {
11915
- type: "formDraft",
11916
- data: result.data,
11917
- schema: this.state.schema
11918
- },
11919
- "*"
11936
+ this.postToParent("formDraft", result.data);
11937
+ return result;
11938
+ }
11939
+ /**
11940
+ * Post form data to the parent frame — only when the host opted in via
11941
+ * `postMessageTarget`. Outside an iframe `window.parent === window`, so an
11942
+ * unconditional post broadcast form data and the full schema to any
11943
+ * embedding page (targetOrigin "*") on every submit. See CHANGELOG 0.6.0.
11944
+ */
11945
+ postToParent(type, data) {
11946
+ const target = this.state.config.postMessageTarget;
11947
+ if (target === "") {
11948
+ throw new Error(
11949
+ 'postMessageTarget: "" is not a valid target origin \u2014 use null to disable posting or "*" to knowingly broadcast'
11920
11950
  );
11921
11951
  }
11922
- return result;
11952
+ if (!target || typeof window === "undefined" || !window.parent) return;
11953
+ window.parent.postMessage(
11954
+ { type, data, schema: this.state.schema },
11955
+ target
11956
+ );
11923
11957
  }
11924
11958
  /**
11925
11959
  * Clear the form - reset all field values to empty while preserving form structure
@@ -12193,6 +12227,9 @@ var FormBuilderInstance = class {
12193
12227
  this.state.debounceTimer = null;
12194
12228
  }
12195
12229
  this.disconnectEnableIfObservers();
12230
+ this.disconnectAutoExpandObservers();
12231
+ this.removeTooltipElements();
12232
+ this.removePrefillHintListener();
12196
12233
  this.state.resourceIndex.clear();
12197
12234
  if (this.state.formRoot) {
12198
12235
  clear(this.state.formRoot);
@@ -12210,6 +12247,24 @@ var FormBuilderInstance = class {
12210
12247
  }
12211
12248
  this.state.enableIfObservers.clear();
12212
12249
  }
12250
+ disconnectAutoExpandObservers() {
12251
+ for (const observer of this.state.autoExpandObservers) {
12252
+ observer.disconnect();
12253
+ }
12254
+ this.state.autoExpandObservers.clear();
12255
+ }
12256
+ removePrefillHintListener() {
12257
+ if (this.prefillHintHandler && this.state.formRoot) {
12258
+ this.state.formRoot.removeEventListener("click", this.prefillHintHandler);
12259
+ }
12260
+ this.prefillHintHandler = null;
12261
+ }
12262
+ removeTooltipElements() {
12263
+ for (const tooltip of this.state.tooltipElements) {
12264
+ tooltip.remove();
12265
+ }
12266
+ this.state.tooltipElements.clear();
12267
+ }
12213
12268
  };
12214
12269
 
12215
12270
  // src/index.ts