@dmitryvim/form-builder 0.5.4 → 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
  }
@@ -62,7 +63,7 @@ function formatFileSize(bytes) {
62
63
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
63
64
  }
64
65
  function serializeHiddenValue(value) {
65
- if (value === null || value === void 0) return "";
66
+ if (value === void 0) return "";
66
67
  return JSON.stringify(value);
67
68
  }
68
69
  function deserializeHiddenValue(raw) {
@@ -73,6 +74,23 @@ function deserializeHiddenValue(raw) {
73
74
  return raw;
74
75
  }
75
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
+ }
76
94
  function createHiddenInput(name, value) {
77
95
  const input = document.createElement("input");
78
96
  input.type = "hidden";
@@ -314,6 +332,14 @@ function validateSchema(schema) {
314
332
  }
315
333
  }
316
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
+ });
317
343
  elements.forEach((element, index) => {
318
344
  const elementPath = `${path}[${index}]`;
319
345
  if (!element.type) {
@@ -482,12 +508,58 @@ function deepEqual(a, b) {
482
508
  }
483
509
 
484
510
  // src/utils/styles.ts
485
- 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);
486
517
  const name = input.getAttribute("name");
487
- if (!name) return;
488
- const doc = input.ownerDocument || document;
489
- const errorNode = doc.getElementById(`error-${name}`);
490
- 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();
491
563
  }
492
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>';
493
565
  function ensureThemingHooks(doc) {
@@ -609,8 +681,8 @@ function ensureThemingHooks(doc) {
609
681
  `;
610
682
  doc.head.appendChild(style);
611
683
  }
612
- function applyAutoExpand(textarea, options = {}) {
613
- var _a;
684
+ function applyAutoExpand(textarea, options) {
685
+ var _a, _b;
614
686
  textarea.style.overflow = "hidden";
615
687
  textarea.style.resize = "none";
616
688
  const minRows = Math.max(1, (_a = options.minRows) != null ? _a : 1);
@@ -638,18 +710,20 @@ function applyAutoExpand(textarea, options = {}) {
638
710
  if (typeof ResizeObserver === "undefined") return;
639
711
  let lastWidth = -1;
640
712
  const ro = new ResizeObserver((entries) => {
641
- var _a2, _b, _c, _d, _e;
713
+ var _a2, _b2, _c, _d, _e, _f;
642
714
  if (!textarea.isConnected) {
643
715
  ro.disconnect();
716
+ (_a2 = options.observers) == null ? void 0 : _a2.delete(ro);
644
717
  return;
645
718
  }
646
719
  const entry = entries[0];
647
- 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;
648
721
  if (w === lastWidth) return;
649
722
  lastWidth = w;
650
723
  resize();
651
724
  });
652
725
  ro.observe(textarea);
726
+ (_b = options.observers) == null ? void 0 : _b.add(ro);
653
727
  }
654
728
  function applySingleLineMode(textarea) {
655
729
  textarea.addEventListener("keydown", (e) => {
@@ -937,7 +1011,7 @@ function createCharCounter(element, input) {
937
1011
  return counter;
938
1012
  }
939
1013
  function renderTextElement(element, ctx, wrapper, pathKey) {
940
- var _a;
1014
+ var _a, _b, _c;
941
1015
  const state = ctx.state;
942
1016
  const readonly = isElementReadonly(element, state, ctx);
943
1017
  const inputWrapper = document.createElement("div");
@@ -965,10 +1039,10 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
965
1039
  `;
966
1040
  textInput.name = pathKey;
967
1041
  textInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
968
- textInput.value = ctx.prefill[element.key] || element.default || "";
1042
+ textInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
969
1043
  textInput.readOnly = readonly;
970
1044
  applySingleLineMode(textInput);
971
- applyAutoExpand(textInput);
1045
+ applyAutoExpand(textInput, { observers: state.autoExpandObservers });
972
1046
  if (!readonly) {
973
1047
  textInput.addEventListener("focus", () => {
974
1048
  textInput.style.borderColor = "var(--fb-border-focus-color)";
@@ -1027,11 +1101,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1027
1101
  const chip = input.closest(".fb-chip");
1028
1102
  const sib = chip == null ? void 0 : chip.nextElementSibling;
1029
1103
  if (sib && sib.classList.contains("error-message")) {
1030
- sib.id = `error-${input.name}`;
1104
+ sib.setAttribute("data-error-for", input.name);
1031
1105
  }
1032
1106
  });
1033
1107
  }
1034
1108
  function addChip(value = "") {
1109
+ var _a2;
1035
1110
  const chip = document.createElement("div");
1036
1111
  chip.className = "fb-chip";
1037
1112
  const dot = document.createElement("span");
@@ -1042,7 +1117,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1042
1117
  input.rows = 1;
1043
1118
  input.className = "fb-chip-input";
1044
1119
  input.value = value;
1045
- input.placeholder = element.placeholder || t("placeholderText", state);
1120
+ input.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
1046
1121
  input.readOnly = readonly;
1047
1122
  chip.appendChild(input);
1048
1123
  if (!readonly && ctx.instance) {
@@ -1056,7 +1131,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1056
1131
  input.addEventListener("input", handleChange);
1057
1132
  }
1058
1133
  applySingleLineMode(input);
1059
- applyAutoExpand(input);
1134
+ applyAutoExpand(input, { observers: state.autoExpandObservers });
1060
1135
  if (!readonly) {
1061
1136
  const rem = document.createElement("button");
1062
1137
  rem.type = "button";
@@ -1064,7 +1139,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1064
1139
  rem.setAttribute("aria-label", t("removeElement", state));
1065
1140
  rem.innerHTML = BIN_ICON_SVG;
1066
1141
  rem.onclick = () => {
1067
- var _a2;
1142
+ var _a3;
1068
1143
  const chips = list.querySelectorAll(".fb-chip");
1069
1144
  const idx = Array.prototype.indexOf.call(chips, chip);
1070
1145
  if (idx < 0) return;
@@ -1078,7 +1153,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
1078
1153
  updateIndices();
1079
1154
  updateAddButton();
1080
1155
  updateRemoveButtons();
1081
- (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1156
+ (_a3 = ctx.instance) == null ? void 0 : _a3.triggerOnChange(pathKey);
1082
1157
  };
1083
1158
  chip.appendChild(rem);
1084
1159
  }
@@ -1123,41 +1198,6 @@ function validateTextElement(element, key, context) {
1123
1198
  var _a, _b, _c;
1124
1199
  const errors = [];
1125
1200
  const { scopeRoot, skipValidation } = context;
1126
- const markValidity = (input, errorMessage) => {
1127
- var _a2, _b2, _c2;
1128
- if (!input) return;
1129
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
1130
- let errorElement = document.getElementById(errorId);
1131
- if (errorMessage) {
1132
- input.classList.add("invalid");
1133
- input.title = errorMessage;
1134
- if (!errorElement) {
1135
- errorElement = document.createElement("div");
1136
- errorElement.id = errorId;
1137
- errorElement.className = "error-message";
1138
- errorElement.style.cssText = `
1139
- color: var(--fb-error-color);
1140
- font-size: var(--fb-font-size-small);
1141
- margin-top: 0.25rem;
1142
- `;
1143
- const chipAncestor = (_a2 = input.closest) == null ? void 0 : _a2.call(input, ".fb-chip");
1144
- const anchor = chipAncestor || input;
1145
- if (anchor.nextSibling) {
1146
- (_b2 = anchor.parentNode) == null ? void 0 : _b2.insertBefore(errorElement, anchor.nextSibling);
1147
- } else {
1148
- (_c2 = anchor.parentNode) == null ? void 0 : _c2.appendChild(errorElement);
1149
- }
1150
- }
1151
- errorElement.textContent = errorMessage;
1152
- errorElement.style.display = "block";
1153
- } else {
1154
- input.classList.remove("invalid");
1155
- input.title = "";
1156
- if (errorElement) {
1157
- errorElement.remove();
1158
- }
1159
- }
1160
- };
1161
1201
  const validateTextInput = (input, val, fieldKey) => {
1162
1202
  let hasError = false;
1163
1203
  const { state } = context;
@@ -1165,12 +1205,12 @@ function validateTextElement(element, key, context) {
1165
1205
  if (element.minLength !== void 0 && element.minLength !== null && val.length < element.minLength) {
1166
1206
  const msg = t("minLength", state, { min: element.minLength });
1167
1207
  errors.push(`${fieldKey}: ${msg}`);
1168
- markValidity(input, msg);
1208
+ markFieldValidity(input, msg);
1169
1209
  hasError = true;
1170
1210
  } else if (element.maxLength !== void 0 && element.maxLength !== null && val.length > element.maxLength) {
1171
1211
  const msg = t("maxLength", state, { max: element.maxLength });
1172
1212
  errors.push(`${fieldKey}: ${msg}`);
1173
- markValidity(input, msg);
1213
+ markFieldValidity(input, msg);
1174
1214
  hasError = true;
1175
1215
  } else if (element.pattern) {
1176
1216
  try {
@@ -1178,19 +1218,19 @@ function validateTextElement(element, key, context) {
1178
1218
  if (!re.test(val)) {
1179
1219
  const msg = t("patternMismatch", state);
1180
1220
  errors.push(`${fieldKey}: ${msg}`);
1181
- markValidity(input, msg);
1221
+ markFieldValidity(input, msg);
1182
1222
  hasError = true;
1183
1223
  }
1184
1224
  } catch {
1185
1225
  const msg = t("invalidPattern", state);
1186
1226
  errors.push(`${fieldKey}: ${msg}`);
1187
- markValidity(input, msg);
1227
+ markFieldValidity(input, msg);
1188
1228
  hasError = true;
1189
1229
  }
1190
1230
  }
1191
1231
  }
1192
1232
  if (!hasError) {
1193
- markValidity(input, null);
1233
+ markFieldValidity(input, null);
1194
1234
  }
1195
1235
  };
1196
1236
  if (element.multiple) {
@@ -1226,7 +1266,7 @@ function validateTextElement(element, key, context) {
1226
1266
  if (!skipValidation && element.required && val === "") {
1227
1267
  const msg = t("required", context.state);
1228
1268
  errors.push(`${key}: ${msg}`);
1229
- markValidity(input, msg);
1269
+ markFieldValidity(input, msg);
1230
1270
  return { value: null, errors };
1231
1271
  }
1232
1272
  if (input) {
@@ -1275,7 +1315,7 @@ function updateTextField(element, fieldPath, value, context) {
1275
1315
 
1276
1316
  // src/components/textarea.ts
1277
1317
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
1278
- var _a, _b;
1318
+ var _a, _b, _c, _d;
1279
1319
  const state = ctx.state;
1280
1320
  const readonly = isElementReadonly(element, state, ctx);
1281
1321
  const textareaWrapper = document.createElement("div");
@@ -1289,8 +1329,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1289
1329
  line-height: var(--fb-line-height, 1.5);
1290
1330
  `;
1291
1331
  textareaInput.name = pathKey;
1292
- textareaInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
1293
- 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 : "";
1294
1334
  textareaInput.readOnly = readonly;
1295
1335
  if (!readonly && ctx.instance) {
1296
1336
  const handleChange = () => {
@@ -1300,7 +1340,10 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1300
1340
  textareaInput.addEventListener("blur", handleChange);
1301
1341
  textareaInput.addEventListener("input", handleChange);
1302
1342
  }
1303
- 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
+ });
1304
1347
  textareaWrapper.appendChild(textareaInput);
1305
1348
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1306
1349
  const counter = createCharCounter(element, textareaInput);
@@ -1332,7 +1375,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1332
1375
  });
1333
1376
  }
1334
1377
  function addTextareaItem(value = "", index = -1) {
1335
- var _a2;
1378
+ var _a2, _b2;
1336
1379
  const itemWrapper = document.createElement("div");
1337
1380
  itemWrapper.className = "multiple-textarea-item";
1338
1381
  const textareaContainer = document.createElement("div");
@@ -1345,7 +1388,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1345
1388
  font-family: var(--fb-font-family);
1346
1389
  line-height: var(--fb-line-height, 1.5);
1347
1390
  `;
1348
- textareaInput.placeholder = element.placeholder || t("placeholderText", state);
1391
+ textareaInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
1349
1392
  textareaInput.value = value;
1350
1393
  textareaInput.readOnly = readonly;
1351
1394
  if (!readonly && ctx.instance) {
@@ -1356,7 +1399,10 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1356
1399
  textareaInput.addEventListener("blur", handleChange);
1357
1400
  textareaInput.addEventListener("input", handleChange);
1358
1401
  }
1359
- 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
+ });
1360
1406
  textareaContainer.appendChild(textareaInput);
1361
1407
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1362
1408
  const counter = createCharCounter(element, textareaInput);
@@ -1579,7 +1625,19 @@ function createNumberRangeHint(element, input) {
1579
1625
  updateColor();
1580
1626
  return hint;
1581
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
+ }
1582
1639
  function renderNumberElement(element, ctx, wrapper, pathKey) {
1640
+ var _a, _b, _c;
1583
1641
  const state = ctx.state;
1584
1642
  const readonly = isElementReadonly(element, state, ctx);
1585
1643
  const inputWrapper = document.createElement("div");
@@ -1587,11 +1645,12 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1587
1645
  const numberInput = document.createElement("input");
1588
1646
  numberInput.type = "number";
1589
1647
  numberInput.name = pathKey;
1590
- numberInput.placeholder = element.placeholder || "0";
1648
+ numberInput.placeholder = (_a = element.placeholder) != null ? _a : "0";
1591
1649
  if (element.min !== void 0) numberInput.min = element.min.toString();
1592
1650
  if (element.max !== void 0) numberInput.max = element.max.toString();
1593
- if (element.step !== void 0) numberInput.step = element.step.toString();
1594
- 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 : "";
1595
1654
  numberInput.readOnly = readonly;
1596
1655
  if (!element.stepper) {
1597
1656
  numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
@@ -1623,7 +1682,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1623
1682
  wrapper.appendChild(inputWrapper);
1624
1683
  }
1625
1684
  function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1626
- var _a, _b;
1685
+ var _a, _b, _c;
1627
1686
  const state = ctx.state;
1628
1687
  const readonly = isElementReadonly(element, state, ctx);
1629
1688
  const prefillValues = ctx.prefill[element.key] || [];
@@ -1631,7 +1690,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1631
1690
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
1632
1691
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
1633
1692
  while (values.length < minCount) {
1634
- values.push(element.default || "");
1693
+ values.push((_c = element.default) != null ? _c : "");
1635
1694
  }
1636
1695
  const container = document.createElement("div");
1637
1696
  container.className = "fb-row";
@@ -1646,6 +1705,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1646
1705
  });
1647
1706
  }
1648
1707
  function addNumberItem(value = "", index = -1) {
1708
+ var _a2;
1649
1709
  const itemWrapper = document.createElement("div");
1650
1710
  itemWrapper.className = "multiple-number-item flex items-center gap-2";
1651
1711
  const inputContainer = document.createElement("div");
@@ -1660,10 +1720,11 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1660
1720
  width: 100%;
1661
1721
  box-sizing: border-box;
1662
1722
  `;
1663
- numberInput.placeholder = element.placeholder || "0";
1723
+ numberInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : "0";
1664
1724
  if (element.min !== void 0) numberInput.min = element.min.toString();
1665
1725
  if (element.max !== void 0) numberInput.max = element.max.toString();
1666
- if (element.step !== void 0) numberInput.step = element.step.toString();
1726
+ numberInput.step = numberStepAttr(element);
1727
+ applyDecimalsMarker(numberInput, element);
1667
1728
  numberInput.value = value.toString();
1668
1729
  numberInput.readOnly = readonly;
1669
1730
  if (!readonly && ctx.instance) {
@@ -1728,12 +1789,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1728
1789
  const handle = createAddItemRow(
1729
1790
  "number",
1730
1791
  () => {
1731
- var _a2;
1732
- values.push(element.default || "");
1733
- addNumberItem(element.default || "");
1792
+ var _a2, _b2, _c2;
1793
+ values.push((_a2 = element.default) != null ? _a2 : "");
1794
+ addNumberItem((_b2 = element.default) != null ? _b2 : "");
1734
1795
  updateAddButton();
1735
1796
  updateRemoveButtons();
1736
- (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
1797
+ (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
1737
1798
  },
1738
1799
  { label: element.addLabel }
1739
1800
  );
@@ -1752,55 +1813,22 @@ function validateNumberElement(element, key, context) {
1752
1813
  var _a, _b, _c;
1753
1814
  const errors = [];
1754
1815
  const { scopeRoot, skipValidation } = context;
1755
- const markValidity = (input, errorMessage) => {
1756
- var _a2, _b2;
1757
- if (!input) return;
1758
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
1759
- let errorElement = document.getElementById(errorId);
1760
- if (errorMessage) {
1761
- input.classList.add("invalid");
1762
- input.title = errorMessage;
1763
- if (!errorElement) {
1764
- errorElement = document.createElement("div");
1765
- errorElement.id = errorId;
1766
- errorElement.className = "error-message";
1767
- errorElement.style.cssText = `
1768
- color: var(--fb-error-color);
1769
- font-size: var(--fb-font-size-small);
1770
- margin-top: 0.25rem;
1771
- `;
1772
- if (input.nextSibling) {
1773
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
1774
- } else {
1775
- (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
1776
- }
1777
- }
1778
- errorElement.textContent = errorMessage;
1779
- errorElement.style.display = "block";
1780
- } else {
1781
- input.classList.remove("invalid");
1782
- input.title = "";
1783
- if (errorElement) {
1784
- errorElement.remove();
1785
- }
1786
- }
1787
- };
1788
1816
  const validateNumberInput = (input, v, fieldKey) => {
1789
1817
  let hasError = false;
1790
1818
  const { state } = context;
1791
1819
  if (!skipValidation && element.min !== void 0 && element.min !== null && v < element.min) {
1792
1820
  const msg = t("minValue", state, { min: element.min });
1793
1821
  errors.push(`${fieldKey}: ${msg}`);
1794
- markValidity(input, msg);
1822
+ markFieldValidity(input, msg);
1795
1823
  hasError = true;
1796
1824
  } else if (!skipValidation && element.max !== void 0 && element.max !== null && v > element.max) {
1797
1825
  const msg = t("maxValue", state, { max: element.max });
1798
1826
  errors.push(`${fieldKey}: ${msg}`);
1799
- markValidity(input, msg);
1827
+ markFieldValidity(input, msg);
1800
1828
  hasError = true;
1801
1829
  }
1802
1830
  if (!hasError) {
1803
- markValidity(input, null);
1831
+ markFieldValidity(input, null);
1804
1832
  }
1805
1833
  };
1806
1834
  if (element.multiple) {
@@ -1813,14 +1841,14 @@ function validateNumberElement(element, key, context) {
1813
1841
  const raw = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
1814
1842
  if (raw === "") {
1815
1843
  values.push(null);
1816
- markValidity(input, null);
1844
+ markFieldValidity(input, null);
1817
1845
  return;
1818
1846
  }
1819
1847
  const v = parseFloat(raw);
1820
1848
  if (!skipValidation && !Number.isFinite(v)) {
1821
1849
  const msg = t("notANumber", context.state);
1822
1850
  errors.push(`${key}[${index}]: ${msg}`);
1823
- markValidity(input, msg);
1851
+ markFieldValidity(input, msg);
1824
1852
  values.push(null);
1825
1853
  return;
1826
1854
  }
@@ -1850,18 +1878,18 @@ function validateNumberElement(element, key, context) {
1850
1878
  if (!skipValidation && element.required && raw === "") {
1851
1879
  const msg = t("required", state);
1852
1880
  errors.push(`${key}: ${msg}`);
1853
- markValidity(input, msg);
1881
+ markFieldValidity(input, msg);
1854
1882
  return { value: null, errors };
1855
1883
  }
1856
1884
  if (raw === "") {
1857
- markValidity(input, null);
1885
+ markFieldValidity(input, null);
1858
1886
  return { value: null, errors };
1859
1887
  }
1860
1888
  const v = parseFloat(raw);
1861
1889
  if (!skipValidation && !Number.isFinite(v)) {
1862
1890
  const msg = t("notANumber", state);
1863
1891
  errors.push(`${key}: ${msg}`);
1864
- markValidity(input, msg);
1892
+ markFieldValidity(input, msg);
1865
1893
  return { value: null, errors };
1866
1894
  }
1867
1895
  validateNumberInput(input, v, key);
@@ -1911,7 +1939,41 @@ function updateNumberField(element, fieldPath, value, context) {
1911
1939
  }
1912
1940
 
1913
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
+ }
1914
1975
  function renderSelectElement(element, ctx, wrapper, pathKey) {
1976
+ var _a;
1915
1977
  const state = ctx.state;
1916
1978
  const readonly = isElementReadonly(element, state, ctx);
1917
1979
  const selectInput = document.createElement("select");
@@ -1923,18 +1985,18 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1923
1985
  `;
1924
1986
  selectInput.name = pathKey;
1925
1987
  selectInput.disabled = readonly;
1926
- (element.options || []).forEach((option) => {
1927
- const optionEl = document.createElement("option");
1928
- optionEl.value = option.value;
1929
- optionEl.textContent = option.label;
1930
- if ((ctx.prefill[element.key] || element.default) === option.value) {
1931
- optionEl.selected = true;
1932
- }
1933
- selectInput.appendChild(optionEl);
1934
- });
1988
+ appendSelectOptions(
1989
+ selectInput,
1990
+ element,
1991
+ (_a = ctx.prefill[element.key]) != null ? _a : element.default,
1992
+ state
1993
+ );
1935
1994
  if (!readonly && ctx.instance) {
1936
1995
  const handleChange = () => {
1937
- ctx.instance.triggerOnChange(pathKey, selectInput.value);
1996
+ ctx.instance.triggerOnChange(
1997
+ pathKey,
1998
+ selectInput.value === "" ? null : selectInput.value
1999
+ );
1938
2000
  };
1939
2001
  selectInput.addEventListener("change", handleChange);
1940
2002
  }
@@ -1947,7 +2009,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1947
2009
  }
1948
2010
  }
1949
2011
  function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1950
- var _a, _b, _c, _d;
2012
+ var _a, _b, _c;
1951
2013
  const state = ctx.state;
1952
2014
  const readonly = isElementReadonly(element, state, ctx);
1953
2015
  const prefillValues = ctx.prefill[element.key] || [];
@@ -1955,7 +2017,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1955
2017
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
1956
2018
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
1957
2019
  while (values.length < minCount) {
1958
- 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 : "");
1959
2021
  }
1960
2022
  const container = document.createElement("div");
1961
2023
  container.className = "fb-row";
@@ -1980,15 +2042,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1980
2042
  font-family: var(--fb-font-family);
1981
2043
  `;
1982
2044
  selectInput.disabled = readonly;
1983
- (element.options || []).forEach((option) => {
1984
- const optionElement = document.createElement("option");
1985
- optionElement.value = option.value;
1986
- optionElement.textContent = option.label;
1987
- if (value === option.value) {
1988
- optionElement.selected = true;
1989
- }
1990
- selectInput.appendChild(optionElement);
1991
- });
2045
+ appendSelectOptions(selectInput, element, value, state);
1992
2046
  if (!readonly && ctx.instance) {
1993
2047
  const handleChange = () => {
1994
2048
  ctx.instance.triggerOnChange(selectInput.name, selectInput.value);
@@ -2042,13 +2096,13 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
2042
2096
  const handle = createAddItemRow(
2043
2097
  "select",
2044
2098
  () => {
2045
- var _a2, _b2, _c2;
2046
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2099
+ var _a2, _b2;
2100
+ const defaultValue = (_a2 = element.default) != null ? _a2 : "";
2047
2101
  values.push(defaultValue);
2048
2102
  addSelectItem(defaultValue);
2049
2103
  updateAddButton();
2050
2104
  updateRemoveButtons();
2051
- (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
2105
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
2052
2106
  },
2053
2107
  { label: element.addLabel }
2054
2108
  );
@@ -2073,39 +2127,6 @@ function validateSelectElement(element, key, context) {
2073
2127
  var _a;
2074
2128
  const errors = [];
2075
2129
  const { scopeRoot, skipValidation } = context;
2076
- const markValidity = (input, errorMessage) => {
2077
- var _a2, _b;
2078
- if (!input) return;
2079
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2080
- let errorElement = document.getElementById(errorId);
2081
- if (errorMessage) {
2082
- input.classList.add("invalid");
2083
- input.title = errorMessage;
2084
- if (!errorElement) {
2085
- errorElement = document.createElement("div");
2086
- errorElement.id = errorId;
2087
- errorElement.className = "error-message";
2088
- errorElement.style.cssText = `
2089
- color: var(--fb-error-color);
2090
- font-size: var(--fb-font-size-small);
2091
- margin-top: 0.25rem;
2092
- `;
2093
- if (input.nextSibling) {
2094
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2095
- } else {
2096
- (_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
2097
- }
2098
- }
2099
- errorElement.textContent = errorMessage;
2100
- errorElement.style.display = "block";
2101
- } else {
2102
- input.classList.remove("invalid");
2103
- input.title = "";
2104
- if (errorElement) {
2105
- errorElement.remove();
2106
- }
2107
- }
2108
- };
2109
2130
  const validateMultipleCount = (key2, values, element2, filterFn) => {
2110
2131
  var _a2, _b;
2111
2132
  if (skipValidation) return;
@@ -2131,27 +2152,36 @@ function validateSelectElement(element, key, context) {
2131
2152
  inputs.forEach((input) => {
2132
2153
  var _a2;
2133
2154
  const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2134
- values.push(val);
2135
- markValidity(input, null);
2155
+ values.push(val === "" ? null : val);
2156
+ markFieldValidity(input, null);
2136
2157
  });
2137
- validateMultipleCount(key, values, element, (v) => v !== "");
2158
+ validateMultipleCount(key, values, element, (v) => v != null);
2138
2159
  return { value: values, errors };
2139
2160
  } else {
2140
- const input = scopeRoot.querySelector(
2141
- `[name="${key}"]`
2142
- );
2161
+ const input = scopeRoot.querySelector(`[name="${key}"]`);
2143
2162
  const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
2144
2163
  if (!skipValidation && element.required && val === "") {
2145
2164
  const msg = t("required", context.state);
2146
2165
  errors.push(`${key}: ${msg}`);
2147
- markValidity(input, msg);
2166
+ markFieldValidity(input, msg);
2148
2167
  return { value: null, errors };
2149
2168
  } else {
2150
- markValidity(input, null);
2169
+ markFieldValidity(input, null);
2151
2170
  }
2152
2171
  return { value: val === "" ? null : val, errors };
2153
2172
  }
2154
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
+ }
2155
2185
  function updateSelectField(element, fieldPath, value, context) {
2156
2186
  const { scopeRoot } = context;
2157
2187
  if ("multiple" in element && element.multiple) {
@@ -2166,10 +2196,17 @@ function updateSelectField(element, fieldPath, value, context) {
2166
2196
  );
2167
2197
  selects.forEach((select, index) => {
2168
2198
  if (index < value.length) {
2169
- 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;
2170
2207
  const options = select.querySelectorAll("option");
2171
2208
  options.forEach((option) => {
2172
- option.selected = option.value === String(value[index]);
2209
+ option.selected = option.value === strValue;
2173
2210
  });
2174
2211
  select.classList.remove("invalid");
2175
2212
  select.title = "";
@@ -2186,10 +2223,12 @@ function updateSelectField(element, fieldPath, value, context) {
2186
2223
  `[name="${fieldPath}"]`
2187
2224
  );
2188
2225
  if (select) {
2189
- select.value = value != null ? String(value) : "";
2226
+ const strValue = value != null ? String(value) : "";
2227
+ assertValueInOptions(select, strValue, fieldPath);
2228
+ select.value = strValue;
2190
2229
  const options = select.querySelectorAll("option");
2191
2230
  options.forEach((option) => {
2192
- option.selected = option.value === String(value);
2231
+ option.selected = option.value === strValue;
2193
2232
  });
2194
2233
  select.classList.remove("invalid");
2195
2234
  select.title = "";
@@ -2387,14 +2426,14 @@ function renderSwitcherElement(element, ctx, wrapper, pathKey) {
2387
2426
  }
2388
2427
  }
2389
2428
  function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2390
- var _a, _b, _c, _d;
2429
+ var _a, _b, _c;
2391
2430
  const state = ctx.state;
2392
2431
  const prefillValues = ctx.prefill[element.key] || [];
2393
2432
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2394
2433
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
2395
2434
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2396
2435
  while (values.length < minCount) {
2397
- 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 : "");
2398
2437
  }
2399
2438
  const readonly = isElementReadonly(element, state, ctx);
2400
2439
  const container = document.createElement("div");
@@ -2489,13 +2528,13 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2489
2528
  const handle = createAddItemRow(
2490
2529
  "switcher",
2491
2530
  () => {
2492
- var _a2, _b2, _c2;
2493
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2531
+ var _a2, _b2;
2532
+ const defaultValue = (_a2 = element.default) != null ? _a2 : "";
2494
2533
  values.push(defaultValue);
2495
2534
  addSwitcherItem(defaultValue);
2496
2535
  updateAddButton();
2497
2536
  updateRemoveButtons();
2498
- (_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
2537
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
2499
2538
  },
2500
2539
  { label: element.addLabel }
2501
2540
  );
@@ -2520,39 +2559,6 @@ function validateSwitcherElement(element, key, context) {
2520
2559
  var _a;
2521
2560
  const errors = [];
2522
2561
  const { scopeRoot, skipValidation } = context;
2523
- const markValidity = (input, errorMessage) => {
2524
- var _a2, _b;
2525
- if (!input) return;
2526
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2527
- let errorElement = document.getElementById(errorId);
2528
- if (errorMessage) {
2529
- input.classList.add("invalid");
2530
- input.title = errorMessage;
2531
- if (!errorElement) {
2532
- errorElement = document.createElement("div");
2533
- errorElement.id = errorId;
2534
- errorElement.className = "error-message";
2535
- errorElement.style.cssText = `
2536
- color: var(--fb-error-color);
2537
- font-size: var(--fb-font-size-small);
2538
- margin-top: 0.25rem;
2539
- `;
2540
- if (input.nextSibling) {
2541
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2542
- } else {
2543
- (_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
2544
- }
2545
- }
2546
- errorElement.textContent = errorMessage;
2547
- errorElement.style.display = "block";
2548
- } else {
2549
- input.classList.remove("invalid");
2550
- input.title = "";
2551
- if (errorElement) {
2552
- errorElement.remove();
2553
- }
2554
- }
2555
- };
2556
2562
  const validateMultipleCount = (fieldKey, values, el, filterFn) => {
2557
2563
  var _a2, _b;
2558
2564
  if (skipValidation) return;
@@ -2581,16 +2587,16 @@ function validateSwitcherElement(element, key, context) {
2581
2587
  inputs.forEach((input) => {
2582
2588
  var _a2;
2583
2589
  const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2584
- values.push(val);
2590
+ values.push(val === "" ? null : val);
2585
2591
  if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
2586
2592
  const msg = t("invalidOption", context.state);
2587
- markValidity(input, msg);
2593
+ markFieldValidity(input, msg);
2588
2594
  errors.push(`${key}: ${msg}`);
2589
2595
  } else {
2590
- markValidity(input, null);
2596
+ markFieldValidity(input, null);
2591
2597
  }
2592
2598
  });
2593
- validateMultipleCount(key, values, element, (v) => v !== "");
2599
+ validateMultipleCount(key, values, element, (v) => v != null);
2594
2600
  return { value: values, errors };
2595
2601
  } else {
2596
2602
  const input = scopeRoot.querySelector(
@@ -2600,16 +2606,16 @@ function validateSwitcherElement(element, key, context) {
2600
2606
  if (!skipValidation && element.required && val === "") {
2601
2607
  const msg = t("required", context.state);
2602
2608
  errors.push(`${key}: ${msg}`);
2603
- markValidity(input, msg);
2609
+ markFieldValidity(input, msg);
2604
2610
  return { value: null, errors };
2605
2611
  }
2606
2612
  if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
2607
2613
  const msg = t("invalidOption", context.state);
2608
2614
  errors.push(`${key}: ${msg}`);
2609
- markValidity(input, msg);
2615
+ markFieldValidity(input, msg);
2610
2616
  return { value: null, errors };
2611
2617
  }
2612
- markValidity(input, null);
2618
+ markFieldValidity(input, null);
2613
2619
  return { value: val === "" ? null : val, errors };
2614
2620
  }
2615
2621
  }
@@ -2784,6 +2790,7 @@ function renderBooleanElement(element, ctx, wrapper, pathKey) {
2784
2790
  const hiddenInput = document.createElement("input");
2785
2791
  hiddenInput.type = "hidden";
2786
2792
  hiddenInput.name = pathKey;
2793
+ hiddenInput.setAttribute("data-boolean-field", "true");
2787
2794
  hiddenInput.value = initial ? "true" : "false";
2788
2795
  const row = document.createElement("div");
2789
2796
  row.className = "fb-toggle-row";
@@ -5910,7 +5917,7 @@ function createReadonlyColourUI(value) {
5910
5917
  container.appendChild(hexText);
5911
5918
  return container;
5912
5919
  }
5913
- function createEditColourUI(value, pathKey, ctx) {
5920
+ function createEditColourUI(value, pathKey, ctx, placeholder) {
5914
5921
  const normalizedValue = normalizeColourValue(value);
5915
5922
  const pickerWrapper = document.createElement("div");
5916
5923
  pickerWrapper.className = "colour-picker-wrapper";
@@ -5934,9 +5941,10 @@ function createEditColourUI(value, pathKey, ctx) {
5934
5941
  const hexInput = document.createElement("input");
5935
5942
  hexInput.type = "text";
5936
5943
  hexInput.className = "colour-hex-input";
5944
+ hexInput.setAttribute("data-colour-field", "true");
5937
5945
  hexInput.name = pathKey;
5938
5946
  hexInput.value = normalizedValue;
5939
- hexInput.placeholder = "#000000";
5947
+ hexInput.placeholder = placeholder != null ? placeholder : "#000000";
5940
5948
  hexInput.style.cssText = `
5941
5949
  width: 100px;
5942
5950
  padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
@@ -6023,14 +6031,20 @@ function createEditColourUI(value, pathKey, ctx) {
6023
6031
  return pickerWrapper;
6024
6032
  }
6025
6033
  function renderColourElement(element, ctx, wrapper, pathKey) {
6034
+ var _a, _b;
6026
6035
  const state = ctx.state;
6027
6036
  const readonly = isElementReadonly(element, state, ctx);
6028
- 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";
6029
6038
  if (readonly) {
6030
6039
  const readonlyUI = createReadonlyColourUI(initialValue);
6031
6040
  wrapper.appendChild(readonlyUI);
6032
6041
  } else {
6033
- const editUI = createEditColourUI(initialValue, pathKey, ctx);
6042
+ const editUI = createEditColourUI(
6043
+ initialValue,
6044
+ pathKey,
6045
+ ctx,
6046
+ element.placeholder
6047
+ );
6034
6048
  wrapper.appendChild(editUI);
6035
6049
  }
6036
6050
  if (!readonly) {
@@ -6045,7 +6059,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
6045
6059
  }
6046
6060
  }
6047
6061
  function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6048
- var _a, _b;
6062
+ var _a, _b, _c;
6049
6063
  const state = ctx.state;
6050
6064
  const readonly = isElementReadonly(element, state, ctx);
6051
6065
  const prefillValues = ctx.prefill[element.key] || [];
@@ -6053,7 +6067,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6053
6067
  const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
6054
6068
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
6055
6069
  while (values.length < minCount) {
6056
- values.push(element.default || "#000000");
6070
+ values.push((_c = element.default) != null ? _c : "#000000");
6057
6071
  }
6058
6072
  const container = document.createElement("div");
6059
6073
  container.className = "fb-row";
@@ -6077,7 +6091,12 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6077
6091
  }
6078
6092
  } else {
6079
6093
  const tempPathKey = `${pathKey}[${container.children.length}]`;
6080
- const editUI = createEditColourUI(value, tempPathKey, ctx);
6094
+ const editUI = createEditColourUI(
6095
+ value,
6096
+ tempPathKey,
6097
+ ctx,
6098
+ element.placeholder
6099
+ );
6081
6100
  editUI.style.flex = "1";
6082
6101
  itemWrapper.appendChild(editUI);
6083
6102
  }
@@ -6140,13 +6159,13 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
6140
6159
  const handle = createAddItemRow(
6141
6160
  "colour",
6142
6161
  () => {
6143
- var _a2;
6144
- const defaultColour = element.default || "#000000";
6162
+ var _a2, _b2;
6163
+ const defaultColour = (_a2 = element.default) != null ? _a2 : "#000000";
6145
6164
  values.push(defaultColour);
6146
6165
  addColourItem(defaultColour);
6147
6166
  updateAddButton();
6148
6167
  updateRemoveButtons();
6149
- (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey);
6168
+ (_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
6150
6169
  },
6151
6170
  { label: element.addLabel }
6152
6171
  );
@@ -6175,59 +6194,26 @@ function validateColourElement(element, key, context) {
6175
6194
  var _a, _b, _c;
6176
6195
  const errors = [];
6177
6196
  const { scopeRoot, skipValidation } = context;
6178
- const markValidity = (input, errorMessage) => {
6179
- var _a2, _b2;
6180
- if (!input) return;
6181
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
6182
- let errorElement = document.getElementById(errorId);
6183
- if (errorMessage) {
6184
- input.classList.add("invalid");
6185
- input.title = errorMessage;
6186
- if (!errorElement) {
6187
- errorElement = document.createElement("div");
6188
- errorElement.id = errorId;
6189
- errorElement.className = "error-message";
6190
- errorElement.style.cssText = `
6191
- color: var(--fb-error-color);
6192
- font-size: var(--fb-font-size-small);
6193
- margin-top: 0.25rem;
6194
- `;
6195
- if (input.nextSibling) {
6196
- (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
6197
- } else {
6198
- (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
6199
- }
6200
- }
6201
- errorElement.textContent = errorMessage;
6202
- errorElement.style.display = "block";
6203
- } else {
6204
- input.classList.remove("invalid");
6205
- input.title = "";
6206
- if (errorElement) {
6207
- errorElement.remove();
6208
- }
6209
- }
6210
- };
6211
6197
  const validateColourValue = (input, val, fieldKey) => {
6212
6198
  const { state } = context;
6213
6199
  if (!val) {
6214
6200
  if (!skipValidation && element.required) {
6215
6201
  const msg = t("required", state);
6216
6202
  errors.push(`${fieldKey}: ${msg}`);
6217
- markValidity(input, msg);
6203
+ markFieldValidity(input, msg);
6218
6204
  return "";
6219
6205
  }
6220
- markValidity(input, null);
6206
+ markFieldValidity(input, null);
6221
6207
  return "";
6222
6208
  }
6223
6209
  const normalized = normalizeColourValue(val);
6224
6210
  if (!skipValidation && !isValidHexColour(normalized)) {
6225
6211
  const msg = t("invalidHexColour", state);
6226
6212
  errors.push(`${fieldKey}: ${msg}`);
6227
- markValidity(input, msg);
6213
+ markFieldValidity(input, msg);
6228
6214
  return val;
6229
6215
  }
6230
- markValidity(input, null);
6216
+ markFieldValidity(input, null);
6231
6217
  return normalized;
6232
6218
  };
6233
6219
  if (element.multiple) {
@@ -6265,7 +6251,7 @@ function validateColourElement(element, key, context) {
6265
6251
  if (!skipValidation && element.required && val === "") {
6266
6252
  const msg = t("required", context.state);
6267
6253
  errors.push(`${key}: ${msg}`);
6268
- markValidity(hexInput, msg);
6254
+ markFieldValidity(hexInput, msg);
6269
6255
  return { value: "", errors };
6270
6256
  }
6271
6257
  const validated = validateColourValue(hexInput, val, key);
@@ -6654,43 +6640,6 @@ function validateSliderElement(element, key, context) {
6654
6640
  const max = element.max;
6655
6641
  const step = (_a = element.step) != null ? _a : 1;
6656
6642
  const scale = element.scale || "linear";
6657
- const markValidity = (input, errorMessage) => {
6658
- var _a2, _b2;
6659
- if (!input) return;
6660
- const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
6661
- let errorElement = document.getElementById(errorId);
6662
- if (errorMessage) {
6663
- input.classList.add("invalid");
6664
- input.title = errorMessage;
6665
- if (!errorElement) {
6666
- errorElement = document.createElement("div");
6667
- errorElement.id = errorId;
6668
- errorElement.className = "error-message";
6669
- errorElement.style.cssText = `
6670
- color: var(--fb-error-color);
6671
- font-size: var(--fb-font-size-small);
6672
- margin-top: 0.25rem;
6673
- `;
6674
- const sliderContainer = input.closest(".slider-container");
6675
- if (sliderContainer && sliderContainer.nextSibling) {
6676
- (_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(
6677
- errorElement,
6678
- sliderContainer.nextSibling
6679
- );
6680
- } else if (sliderContainer) {
6681
- (_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
6682
- }
6683
- }
6684
- errorElement.textContent = errorMessage;
6685
- errorElement.style.display = "block";
6686
- } else {
6687
- input.classList.remove("invalid");
6688
- input.title = "";
6689
- if (errorElement) {
6690
- errorElement.remove();
6691
- }
6692
- }
6693
- };
6694
6643
  const validateSliderValue = (slider, fieldKey) => {
6695
6644
  const { state } = context;
6696
6645
  const rawValue = slider.value;
@@ -6698,10 +6647,10 @@ function validateSliderElement(element, key, context) {
6698
6647
  if (!skipValidation && element.required) {
6699
6648
  const msg = t("required", state);
6700
6649
  errors.push(`${fieldKey}: ${msg}`);
6701
- markValidity(slider, msg);
6650
+ markFieldValidity(slider, msg);
6702
6651
  return null;
6703
6652
  }
6704
- markValidity(slider, null);
6653
+ markFieldValidity(slider, null);
6705
6654
  return null;
6706
6655
  }
6707
6656
  let value;
@@ -6717,17 +6666,17 @@ function validateSliderElement(element, key, context) {
6717
6666
  if (value < min) {
6718
6667
  const msg = t("minValue", state, { min });
6719
6668
  errors.push(`${fieldKey}: ${msg}`);
6720
- markValidity(slider, msg);
6669
+ markFieldValidity(slider, msg);
6721
6670
  return value;
6722
6671
  }
6723
6672
  if (value > max) {
6724
6673
  const msg = t("maxValue", state, { max });
6725
6674
  errors.push(`${fieldKey}: ${msg}`);
6726
- markValidity(slider, msg);
6675
+ markFieldValidity(slider, msg);
6727
6676
  return value;
6728
6677
  }
6729
6678
  }
6730
- markValidity(slider, null);
6679
+ markFieldValidity(slider, null);
6731
6680
  return value;
6732
6681
  };
6733
6682
  if (element.multiple) {
@@ -6873,22 +6822,12 @@ function extractRootFormData(formRoot) {
6873
6822
  inputs.forEach((input) => {
6874
6823
  const fieldName = input.getAttribute("name");
6875
6824
  if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
6876
- if (input instanceof HTMLSelectElement) {
6877
- data[fieldName] = input.value;
6878
- } else if (input instanceof HTMLInputElement) {
6879
- if (input.type === "checkbox") {
6880
- data[fieldName] = input.checked;
6881
- } else if (input.type === "radio") {
6882
- if (input.checked) {
6883
- data[fieldName] = input.value;
6884
- }
6885
- } else if (input.dataset.hiddenField) {
6886
- data[fieldName] = deserializeHiddenValue(input.value);
6887
- } else {
6825
+ if (input instanceof HTMLInputElement && input.type === "radio") {
6826
+ if (input.checked) {
6888
6827
  data[fieldName] = input.value;
6889
6828
  }
6890
- } else if (input instanceof HTMLTextAreaElement) {
6891
- data[fieldName] = input.value;
6829
+ } else {
6830
+ data[fieldName] = readTypedInputValue(input);
6892
6831
  }
6893
6832
  }
6894
6833
  });
@@ -6957,9 +6896,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
6957
6896
  inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
6958
6897
  };
6959
6898
  element.elements.forEach((child) => {
6960
- var _a2, _b2;
6899
+ var _a2;
6961
6900
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
6962
- 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;
6963
6902
  itemsWrap.appendChild(
6964
6903
  createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
6965
6904
  );
@@ -7061,9 +7000,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
7061
7000
  isSlides ? void 0 : element.columns
7062
7001
  );
7063
7002
  element.elements.forEach((child) => {
7064
- var _a2, _b2;
7003
+ var _a2;
7065
7004
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
7066
- 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;
7067
7006
  childWrapper.appendChild(
7068
7007
  createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
7069
7008
  );
@@ -7174,19 +7113,16 @@ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
7174
7113
  }
7175
7114
  }
7176
7115
  }
7177
- var validateElementFunc = null;
7178
- function setValidateElement(fn) {
7179
- validateElementFunc = fn;
7180
- }
7181
- function validateElement(element, ctx, customScopeRoot) {
7182
- if (!validateElementFunc) {
7116
+ function requireValidateElement(context) {
7117
+ if (!context.validateElement) {
7183
7118
  throw new Error(
7184
- "validateElement not initialized. Should be set from FormBuilderInstance"
7119
+ "validateContainerElement: context.validateElement missing \u2014 container validation requires the instance validator"
7185
7120
  );
7186
7121
  }
7187
- return validateElementFunc(element, ctx, customScopeRoot);
7122
+ return context.validateElement;
7188
7123
  }
7189
7124
  function validateContainerElement(element, key, context) {
7125
+ const validateChild = requireValidateElement(context);
7190
7126
  const errors = [];
7191
7127
  const { scopeRoot, skipValidation, path } = context;
7192
7128
  if (!("elements" in element)) {
@@ -7239,7 +7175,7 @@ function validateContainerElement(element, key, context) {
7239
7175
  }
7240
7176
  }
7241
7177
  const childKey = `${key}[${domIndex}].${child.key}`;
7242
- const childResult = validateElement(
7178
+ const childResult = validateChild(
7243
7179
  { ...child, key: childKey },
7244
7180
  { path },
7245
7181
  itemContainer
@@ -7281,7 +7217,7 @@ function validateContainerElement(element, key, context) {
7281
7217
  }
7282
7218
  {
7283
7219
  const childKey = `${key}.${child.key}`;
7284
- const childResult = validateElement(
7220
+ const childResult = validateChild(
7285
7221
  { ...child, key: childKey },
7286
7222
  { path },
7287
7223
  containerContainer
@@ -7784,7 +7720,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
7784
7720
  rebuild();
7785
7721
  } catch (e) {
7786
7722
  const errMsg = e instanceof Error ? e.message : String(e);
7787
- console.error(t("tableImportError", state).replace("{error}", errMsg));
7723
+ console.error(t("tableImportError", state, { error: errMsg }));
7788
7724
  } finally {
7789
7725
  overlay.remove();
7790
7726
  }
@@ -8744,7 +8680,7 @@ function updateTableField(element, fieldPath, value, context) {
8744
8680
  }
8745
8681
 
8746
8682
  // src/components/richinput.ts
8747
- function applyAutoExpand2(textarea, backdrop) {
8683
+ function applyAutoExpand2(textarea, backdrop, observers) {
8748
8684
  textarea.style.overflow = "hidden";
8749
8685
  textarea.style.resize = "none";
8750
8686
  const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
@@ -8767,6 +8703,7 @@ function applyAutoExpand2(textarea, backdrop) {
8767
8703
  var _a, _b, _c, _d, _e;
8768
8704
  if (!textarea.isConnected) {
8769
8705
  ro.disconnect();
8706
+ observers.delete(ro);
8770
8707
  return;
8771
8708
  }
8772
8709
  const entry = entries[0];
@@ -8776,6 +8713,7 @@ function applyAutoExpand2(textarea, backdrop) {
8776
8713
  resize();
8777
8714
  });
8778
8715
  ro.observe(textarea);
8716
+ observers.add(ro);
8779
8717
  }
8780
8718
  function buildFileLabels(files, state) {
8781
8719
  var _a, _b, _c, _d;
@@ -9204,7 +9142,7 @@ function filterFilesForDropdown(query, files, labels) {
9204
9142
  var TEXTAREA_FONT = "font-size: var(--fb-font-size, 14px); font-family: var(--fb-font-family, inherit); line-height: 1.6;";
9205
9143
  var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
9206
9144
  function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9207
- var _a;
9145
+ var _a, _b;
9208
9146
  const state = ctx.state;
9209
9147
  const files = [...initialValue.files];
9210
9148
  const dropdownState = {
@@ -9218,12 +9156,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9218
9156
  hiddenInput.type = "hidden";
9219
9157
  hiddenInput.name = pathKey;
9220
9158
  function getCurrentValue() {
9221
- var _a2, _b;
9159
+ var _a2, _b2;
9222
9160
  const rawText = textarea.value;
9223
9161
  const nameToRid = buildNameToRid(files, state);
9224
9162
  const submissionText = rawText ? replaceFilenamesWithRids(rawText, nameToRid) : null;
9225
9163
  const textKey = (_a2 = element.textKey) != null ? _a2 : "text";
9226
- const filesKey = (_b = element.filesKey) != null ? _b : "files";
9164
+ const filesKey = (_b2 = element.filesKey) != null ? _b2 : "files";
9227
9165
  return {
9228
9166
  [textKey]: rawText === "" ? null : submissionText,
9229
9167
  [filesKey]: [...files]
@@ -9305,14 +9243,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9305
9243
  }
9306
9244
  });
9307
9245
  outerDiv.addEventListener("drop", (e) => {
9308
- var _a2, _b;
9246
+ var _a2, _b2;
9309
9247
  e.preventDefault();
9310
9248
  dragCounter = 0;
9311
9249
  outerDiv.style.borderColor = "var(--fb-border-color, #d1d5db)";
9312
9250
  outerDiv.style.boxShadow = "none";
9313
9251
  const droppedFiles = (_a2 = e.dataTransfer) == null ? void 0 : _a2.files;
9314
9252
  if (!droppedFiles || !state.config.uploadFile) return;
9315
- const maxFiles = (_b = element.maxFiles) != null ? _b : Infinity;
9253
+ const maxFiles = (_b2 = element.maxFiles) != null ? _b2 : Infinity;
9316
9254
  for (let i = 0; i < droppedFiles.length; i++) {
9317
9255
  if (files.length >= maxFiles) {
9318
9256
  showUploadError(
@@ -9361,8 +9299,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9361
9299
  `;
9362
9300
  const textarea = document.createElement("textarea");
9363
9301
  textarea.name = `${pathKey}__text`;
9364
- textarea.placeholder = element.placeholder || t("richinputPlaceholder", state);
9365
- 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 : "";
9366
9304
  textarea.value = rawInitialText ? replaceRidsWithFilenames(rawInitialText, files, state) : "";
9367
9305
  textarea.style.cssText = `
9368
9306
  width: 100%;
@@ -9378,14 +9316,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9378
9316
  z-index: 1;
9379
9317
  caret-color: var(--fb-text-color, #111827);
9380
9318
  `;
9381
- applyAutoExpand2(textarea, backdrop);
9319
+ applyAutoExpand2(textarea, backdrop, ctx.state.autoExpandObservers);
9382
9320
  textarea.addEventListener("scroll", () => {
9383
9321
  backdrop.scrollTop = textarea.scrollTop;
9384
9322
  });
9385
9323
  let mentionTooltip = null;
9386
9324
  backdrop.addEventListener("mouseover", (e) => {
9387
- var _a2, _b;
9388
- 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(
9389
9327
  _a2,
9390
9328
  "mark"
9391
9329
  );
@@ -9394,8 +9332,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9394
9332
  mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
9395
9333
  });
9396
9334
  backdrop.addEventListener("mouseout", (e) => {
9397
- var _a2, _b, _c;
9398
- 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(
9399
9337
  _a2,
9400
9338
  "mark"
9401
9339
  );
@@ -9405,8 +9343,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9405
9343
  mentionTooltip = removePortalTooltip(mentionTooltip);
9406
9344
  });
9407
9345
  backdrop.addEventListener("mousedown", (e) => {
9408
- var _a2, _b;
9409
- 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(
9410
9348
  _a2,
9411
9349
  "mark"
9412
9350
  );
@@ -9579,8 +9517,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9579
9517
  dropdown.appendChild(item);
9580
9518
  });
9581
9519
  dropdown.onmousemove = (e) => {
9582
- var _a2, _b, _c;
9583
- 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(
9584
9522
  _a2,
9585
9523
  ".fb-richinput-dropdown-item"
9586
9524
  );
@@ -9596,10 +9534,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9596
9534
  dropdownState.selectedIndex = newIdx;
9597
9535
  };
9598
9536
  dropdown.onmousedown = (e) => {
9599
- var _a2, _b;
9537
+ var _a2, _b2;
9600
9538
  e.preventDefault();
9601
9539
  e.stopPropagation();
9602
- 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(
9603
9541
  _a2,
9604
9542
  ".fb-richinput-dropdown-item"
9605
9543
  );
@@ -9627,9 +9565,9 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9627
9565
  dropdownState.open = false;
9628
9566
  }
9629
9567
  function insertMention(rid) {
9630
- var _a2, _b, _c, _d;
9568
+ var _a2, _b2, _c, _d;
9631
9569
  const labels = buildFileLabelsFromClosure();
9632
- 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;
9633
9571
  const cursorPos = (_d = textarea.selectionStart) != null ? _d : 0;
9634
9572
  const before = textarea.value.slice(0, dropdownState.triggerPos);
9635
9573
  const after = textarea.value.slice(cursorPos);
@@ -9721,10 +9659,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9721
9659
  thumbWrapper.appendChild(thumbInner);
9722
9660
  const tooltipHandle = createTooltipHandle();
9723
9661
  const doMention = () => {
9724
- var _a2, _b, _c;
9662
+ var _a2, _b2, _c;
9725
9663
  const cursorPos = (_a2 = textarea.selectionStart) != null ? _a2 : textarea.value.length;
9726
9664
  const labels = buildFileLabelsFromClosure();
9727
- 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;
9728
9666
  const before = textarea.value.slice(0, cursorPos);
9729
9667
  const after = textarea.value.slice(cursorPos);
9730
9668
  const prefix = before.length > 0 && !/[\s\n]$/.test(before) ? "\n" : "";
@@ -9802,12 +9740,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
9802
9740
  writeHidden();
9803
9741
  (_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey, getCurrentValue());
9804
9742
  }).catch((err) => {
9805
- var _a2, _b;
9743
+ var _a2, _b2;
9806
9744
  const idx = files.indexOf(tempId);
9807
9745
  if (idx !== -1) files.splice(idx, 1);
9808
9746
  state.resourceIndex.delete(tempId);
9809
9747
  renderFilesRow();
9810
- (_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);
9811
9749
  });
9812
9750
  }
9813
9751
  fileInput.addEventListener("change", () => {
@@ -10379,12 +10317,7 @@ function validateHiddenElement(element, key, context) {
10379
10317
  const input = scopeRoot.querySelector(
10380
10318
  `input[type="hidden"][data-hidden-field="true"][name="${key}"]`
10381
10319
  );
10382
- const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
10383
- if (raw === "") {
10384
- const defaultVal = "default" in element ? element.default : null;
10385
- return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
10386
- }
10387
- return { value: deserializeHiddenValue(raw), errors: [] };
10320
+ return { value: deserializeHiddenValue((_a = input == null ? void 0 : input.value) != null ? _a : ""), errors: [] };
10388
10321
  }
10389
10322
  function updateHiddenField(_element, fieldPath, value, context) {
10390
10323
  const { scopeRoot } = context;
@@ -10576,25 +10509,13 @@ function extractDOMValue(fieldPath, formRoot) {
10576
10509
  if (!input) {
10577
10510
  return void 0;
10578
10511
  }
10579
- if (input instanceof HTMLSelectElement) {
10580
- return input.value;
10581
- } else if (input instanceof HTMLInputElement) {
10582
- if (input.type === "checkbox") {
10583
- return input.checked;
10584
- } else if (input.type === "radio") {
10585
- const checked = formRoot.querySelector(
10586
- `[name="${fieldPath}"]:checked`
10587
- );
10588
- return checked ? checked.value : void 0;
10589
- } else if (input.dataset.hiddenField) {
10590
- return deserializeHiddenValue(input.value);
10591
- } else {
10592
- return input.value;
10593
- }
10594
- } else if (input instanceof HTMLTextAreaElement) {
10595
- 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;
10596
10517
  }
10597
- return void 0;
10518
+ return readTypedInputValue(input);
10598
10519
  }
10599
10520
  function buildScopedDataAtPath(path, value) {
10600
10521
  const segments = path.match(/[^.[\]]+|\[\d+\]/g);
@@ -10726,11 +10647,19 @@ function createFieldLabel(element) {
10726
10647
  }
10727
10648
  return title;
10728
10649
  }
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
+ }
10729
10657
  function createInfoButton(element, state) {
10730
10658
  const infoBtn = document.createElement("button");
10731
10659
  infoBtn.type = "button";
10732
10660
  infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
10733
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);
10734
10663
  const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
10735
10664
  const tooltip = document.createElement("div");
10736
10665
  tooltip.id = tooltipId;
@@ -10911,6 +10840,7 @@ var defaultConfig = {
10911
10840
  onDownloadError: null,
10912
10841
  debounceMs: 300,
10913
10842
  verboseErrors: false,
10843
+ postMessageTarget: null,
10914
10844
  enableFilePreview: true,
10915
10845
  maxPreviewSize: "200px",
10916
10846
  readonly: false,
@@ -10932,6 +10862,7 @@ var defaultConfig = {
10932
10862
  openInNewTab: "Open in new tab",
10933
10863
  changeButton: "Change",
10934
10864
  placeholderText: "Enter text",
10865
+ selectPlaceholder: "Select\u2026",
10935
10866
  previewAlt: "Preview",
10936
10867
  previewUnavailable: "Preview unavailable",
10937
10868
  previewError: "Preview error",
@@ -11007,6 +10938,7 @@ var defaultConfig = {
11007
10938
  openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
11008
10939
  changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
11009
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",
11010
10942
  previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
11011
10943
  previewUnavailable: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
11012
10944
  previewError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430",
@@ -11071,20 +11003,23 @@ var defaultConfig = {
11071
11003
  },
11072
11004
  theme: {}
11073
11005
  };
11074
- function createInstanceState(config) {
11075
- const mergedTranslations = {
11076
- ...defaultConfig.translations
11077
- };
11078
- if (config == null ? void 0 : config.translations) {
11079
- for (const [locale, userTranslations] of Object.entries(
11080
- config.translations
11081
- )) {
11082
- mergedTranslations[locale] = {
11083
- ...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] || {},
11084
11012
  ...userTranslations
11085
11013
  };
11086
11014
  }
11087
11015
  }
11016
+ return merged;
11017
+ }
11018
+ function createInstanceState(config) {
11019
+ const mergedTranslations = mergeTranslations(
11020
+ defaultConfig.translations,
11021
+ config == null ? void 0 : config.translations
11022
+ );
11088
11023
  return {
11089
11024
  schema: null,
11090
11025
  formRoot: null,
@@ -11101,6 +11036,7 @@ function createInstanceState(config) {
11101
11036
  syntheticElementIds: /* @__PURE__ */ new WeakMap(),
11102
11037
  syntheticElementIdCounter: 0,
11103
11038
  enableIfObservers: /* @__PURE__ */ new Set(),
11039
+ autoExpandObservers: /* @__PURE__ */ new Set(),
11104
11040
  tooltipElements: /* @__PURE__ */ new Set()
11105
11041
  };
11106
11042
  }
@@ -11402,6 +11338,11 @@ function findOwnField(scope, lookupKey, ownBoundary) {
11402
11338
  }
11403
11339
  var FormBuilderInstance = class {
11404
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;
11405
11346
  this.instanceId = generateInstanceId();
11406
11347
  this.state = createInstanceState(config);
11407
11348
  if (this.state.config.verboseErrors) {
@@ -11435,10 +11376,21 @@ var FormBuilderInstance = class {
11435
11376
  this.state.formRoot = element;
11436
11377
  }
11437
11378
  /**
11438
- * 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.
11439
11382
  */
11440
11383
  configure(config) {
11441
- 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 });
11442
11394
  }
11443
11395
  /**
11444
11396
  * Set file upload handler
@@ -11471,12 +11423,16 @@ var FormBuilderInstance = class {
11471
11423
  this.state.config.readonly = mode === "readonly";
11472
11424
  }
11473
11425
  /**
11474
- * Set locale
11426
+ * Set locale. Custom locales are allowed — their translations must have
11427
+ * been provided via the constructor or configure() first.
11475
11428
  */
11476
11429
  setLocale(locale) {
11477
- if (this.state.config.translations[locale]) {
11478
- 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
+ );
11479
11434
  }
11435
+ this.state.config.locale = locale;
11480
11436
  }
11481
11437
  /**
11482
11438
  * Trigger onChange callbacks with debouncing
@@ -11829,11 +11785,13 @@ var FormBuilderInstance = class {
11829
11785
  renderForm(root, schema, prefill, actions) {
11830
11786
  const errors = validateSchema(schema);
11831
11787
  if (errors.length > 0) {
11832
- console.error("Schema validation errors:", errors);
11833
- return;
11788
+ throw new Error(`renderForm: invalid schema:
11789
+ - ${errors.join("\n- ")}`);
11834
11790
  }
11835
11791
  this.disconnectEnableIfObservers();
11792
+ this.disconnectAutoExpandObservers();
11836
11793
  this.removeTooltipElements();
11794
+ this.removePrefillHintListener();
11837
11795
  this.state.formRoot = root;
11838
11796
  this.state.schema = schema;
11839
11797
  this.state.externalActions = actions || null;
@@ -11855,9 +11813,9 @@ var FormBuilderInstance = class {
11855
11813
  fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
11856
11814
  }
11857
11815
  schema.elements.forEach((element) => {
11858
- var _a, _b;
11816
+ var _a;
11859
11817
  if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
11860
- 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;
11861
11819
  fieldsWrapper.appendChild(createHiddenInput(element.key, val));
11862
11820
  return;
11863
11821
  }
@@ -11874,7 +11832,10 @@ var FormBuilderInstance = class {
11874
11832
  rootContainer.appendChild(fieldsWrapper);
11875
11833
  root.appendChild(rootContainer);
11876
11834
  if (!this.state.config.readonly) {
11877
- root.addEventListener("click", this.handlePrefillHintClick.bind(this));
11835
+ this.prefillHintHandler = this.handlePrefillHintClick.bind(
11836
+ this
11837
+ );
11838
+ root.addEventListener("click", this.prefillHintHandler);
11878
11839
  }
11879
11840
  if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
11880
11841
  this.renderExternalActions();
@@ -11890,7 +11851,7 @@ var FormBuilderInstance = class {
11890
11851
  return { valid: true, errors: [], data: {} };
11891
11852
  const errors = [];
11892
11853
  const data = {};
11893
- const validateElement2 = (element, ctx, customScopeRoot = null) => {
11854
+ const validateElement = (element, ctx, customScopeRoot = null) => {
11894
11855
  var _a;
11895
11856
  const key = (_a = element.key) != null ? _a : "";
11896
11857
  const scopeRoot = customScopeRoot || this.state.formRoot;
@@ -11899,7 +11860,10 @@ var FormBuilderInstance = class {
11899
11860
  state: this.state,
11900
11861
  instance: this,
11901
11862
  path: ctx.path,
11902
- skipValidation
11863
+ skipValidation,
11864
+ // Containers recurse into their children through this — threaded per
11865
+ // pass, never module state (see ComponentContext.validateElement).
11866
+ validateElement
11903
11867
  };
11904
11868
  const componentResult = validateElementWithComponent(
11905
11869
  element,
@@ -11917,7 +11881,6 @@ var FormBuilderInstance = class {
11917
11881
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
11918
11882
  return { value: null, spread: false };
11919
11883
  };
11920
- setValidateElement(validateElement2);
11921
11884
  this.state.schema.elements.forEach((element) => {
11922
11885
  if (element.enableIf) {
11923
11886
  try {
@@ -11935,7 +11898,7 @@ var FormBuilderInstance = class {
11935
11898
  if (element.type === "markdown") {
11936
11899
  return;
11937
11900
  }
11938
- const result = validateElement2(element, { path: "" });
11901
+ const result = validateElement(element, { path: "" });
11939
11902
  if (result.skip) return;
11940
11903
  if (result.spread && result.value !== null && typeof result.value === "object") {
11941
11904
  Object.assign(data, result.value);
@@ -11961,16 +11924,7 @@ var FormBuilderInstance = class {
11961
11924
  submitForm() {
11962
11925
  const result = this.validateForm(false);
11963
11926
  if (result.valid) {
11964
- if (typeof window !== "undefined" && window.parent) {
11965
- window.parent.postMessage(
11966
- {
11967
- type: "formSubmit",
11968
- data: result.data,
11969
- schema: this.state.schema
11970
- },
11971
- "*"
11972
- );
11973
- }
11927
+ this.postToParent("formSubmit", result.data);
11974
11928
  }
11975
11929
  return result;
11976
11930
  }
@@ -11979,17 +11933,27 @@ var FormBuilderInstance = class {
11979
11933
  */
11980
11934
  saveDraft() {
11981
11935
  const result = this.validateForm(true);
11982
- if (typeof window !== "undefined" && window.parent) {
11983
- window.parent.postMessage(
11984
- {
11985
- type: "formDraft",
11986
- data: result.data,
11987
- schema: this.state.schema
11988
- },
11989
- "*"
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'
11990
11950
  );
11991
11951
  }
11992
- 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
+ );
11993
11957
  }
11994
11958
  /**
11995
11959
  * Clear the form - reset all field values to empty while preserving form structure
@@ -12263,7 +12227,9 @@ var FormBuilderInstance = class {
12263
12227
  this.state.debounceTimer = null;
12264
12228
  }
12265
12229
  this.disconnectEnableIfObservers();
12230
+ this.disconnectAutoExpandObservers();
12266
12231
  this.removeTooltipElements();
12232
+ this.removePrefillHintListener();
12267
12233
  this.state.resourceIndex.clear();
12268
12234
  if (this.state.formRoot) {
12269
12235
  clear(this.state.formRoot);
@@ -12281,6 +12247,18 @@ var FormBuilderInstance = class {
12281
12247
  }
12282
12248
  this.state.enableIfObservers.clear();
12283
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
+ }
12284
12262
  removeTooltipElements() {
12285
12263
  for (const tooltip of this.state.tooltipElements) {
12286
12264
  tooltip.remove();