@dmitryvim/form-builder 0.2.24 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -6,7 +6,10 @@ function t(key, state, params) {
6
6
  let text = localeTranslations?.[key] || fallbackTranslations?.[key] || key;
7
7
  if (params) {
8
8
  for (const [paramKey, paramValue] of Object.entries(params)) {
9
- text = text.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
9
+ text = text.replace(
10
+ new RegExp(`\\{${paramKey}\\}`, "g"),
11
+ String(paramValue)
12
+ );
10
13
  }
11
14
  }
12
15
  return text;
@@ -249,6 +252,9 @@ function validateSchema(schema) {
249
252
  }
250
253
 
251
254
  // src/utils/helpers.ts
255
+ function isElementReadonly(element, state, ctx) {
256
+ return element.readonly === true || state.config.readonly === true || ctx?.inheritedReadonly === true;
257
+ }
252
258
  function isPlainObject(obj) {
253
259
  return obj && typeof obj === "object" && obj.constructor === Object;
254
260
  }
@@ -407,6 +413,7 @@ function createCharCounter(element, input, isTextarea = false) {
407
413
  }
408
414
  function renderTextElement(element, ctx, wrapper, pathKey) {
409
415
  const state = ctx.state;
416
+ const readonly = isElementReadonly(element, state, ctx);
410
417
  const inputWrapper = document.createElement("div");
411
418
  inputWrapper.style.cssText = "position: relative;";
412
419
  const textInput = document.createElement("input");
@@ -417,7 +424,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
417
424
  padding-right: 60px;
418
425
  border: var(--fb-border-width) solid var(--fb-border-color);
419
426
  border-radius: var(--fb-border-radius);
420
- background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
427
+ background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
421
428
  color: var(--fb-text-color);
422
429
  font-size: var(--fb-font-size);
423
430
  font-family: var(--fb-font-family);
@@ -428,8 +435,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
428
435
  textInput.name = pathKey;
429
436
  textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
430
437
  textInput.value = ctx.prefill[element.key] || element.default || "";
431
- textInput.readOnly = state.config.readonly;
432
- if (!state.config.readonly) {
438
+ textInput.readOnly = readonly;
439
+ if (!readonly) {
433
440
  textInput.addEventListener("focus", () => {
434
441
  textInput.style.borderColor = "var(--fb-border-focus-color)";
435
442
  textInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
@@ -450,7 +457,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
450
457
  }
451
458
  });
452
459
  }
453
- if (!state.config.readonly && ctx.instance) {
460
+ if (!readonly && ctx.instance) {
454
461
  const handleChange = () => {
455
462
  const value = textInput.value === "" ? null : textInput.value;
456
463
  ctx.instance.triggerOnChange(pathKey, value);
@@ -459,7 +466,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
459
466
  textInput.addEventListener("input", handleChange);
460
467
  }
461
468
  inputWrapper.appendChild(textInput);
462
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
469
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
463
470
  const counter = createCharCounter(element, textInput, false);
464
471
  inputWrapper.appendChild(counter);
465
472
  }
@@ -467,6 +474,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
467
474
  }
468
475
  function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
469
476
  const state = ctx.state;
477
+ const readonly = isElementReadonly(element, state, ctx);
470
478
  const prefillValues = ctx.prefill[element.key] || [];
471
479
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
472
480
  const minCount = element.minCount ?? 1;
@@ -498,7 +506,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
498
506
  padding-right: 60px;
499
507
  border: var(--fb-border-width) solid var(--fb-border-color);
500
508
  border-radius: var(--fb-border-radius);
501
- background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
509
+ background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
502
510
  color: var(--fb-text-color);
503
511
  font-size: var(--fb-font-size);
504
512
  font-family: var(--fb-font-family);
@@ -508,8 +516,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
508
516
  `;
509
517
  textInput.placeholder = element.placeholder || t("placeholderText", state);
510
518
  textInput.value = value;
511
- textInput.readOnly = state.config.readonly;
512
- if (!state.config.readonly) {
519
+ textInput.readOnly = readonly;
520
+ if (!readonly) {
513
521
  textInput.addEventListener("focus", () => {
514
522
  textInput.style.borderColor = "var(--fb-border-focus-color)";
515
523
  textInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
@@ -530,7 +538,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
530
538
  }
531
539
  });
532
540
  }
533
- if (!state.config.readonly && ctx.instance) {
541
+ if (!readonly && ctx.instance) {
534
542
  const handleChange = () => {
535
543
  const value2 = textInput.value === "" ? null : textInput.value;
536
544
  ctx.instance.triggerOnChange(textInput.name, value2);
@@ -539,7 +547,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
539
547
  textInput.addEventListener("input", handleChange);
540
548
  }
541
549
  inputContainer.appendChild(textInput);
542
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
550
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
543
551
  const counter = createCharCounter(element, textInput, false);
544
552
  inputContainer.appendChild(counter);
545
553
  }
@@ -553,7 +561,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
553
561
  return itemWrapper;
554
562
  }
555
563
  function updateRemoveButtons() {
556
- if (state.config.readonly) return;
564
+ if (readonly) return;
557
565
  const items = container.querySelectorAll(".multiple-text-item");
558
566
  const currentCount = items.length;
559
567
  items.forEach((item) => {
@@ -598,7 +606,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
598
606
  }
599
607
  let addRow = null;
600
608
  let countDisplay = null;
601
- if (!state.config.readonly) {
609
+ if (!readonly) {
602
610
  addRow = document.createElement("div");
603
611
  addRow.className = "flex items-center gap-3 mt-2";
604
612
  const addBtn = document.createElement("button");
@@ -806,6 +814,7 @@ function applyAutoExpand(textarea) {
806
814
  }
807
815
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
808
816
  const state = ctx.state;
817
+ const readonly = isElementReadonly(element, state, ctx);
809
818
  const textareaWrapper = document.createElement("div");
810
819
  textareaWrapper.style.cssText = "position: relative;";
811
820
  const textareaInput = document.createElement("textarea");
@@ -815,8 +824,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
815
824
  textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
816
825
  textareaInput.rows = element.rows || 4;
817
826
  textareaInput.value = ctx.prefill[element.key] || element.default || "";
818
- textareaInput.readOnly = state.config.readonly;
819
- if (!state.config.readonly && ctx.instance) {
827
+ textareaInput.readOnly = readonly;
828
+ if (!readonly && ctx.instance) {
820
829
  const handleChange = () => {
821
830
  const value = textareaInput.value === "" ? null : textareaInput.value;
822
831
  ctx.instance.triggerOnChange(pathKey, value);
@@ -824,11 +833,11 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
824
833
  textareaInput.addEventListener("blur", handleChange);
825
834
  textareaInput.addEventListener("input", handleChange);
826
835
  }
827
- if (element.autoExpand || state.config.readonly) {
836
+ if (element.autoExpand || readonly) {
828
837
  applyAutoExpand(textareaInput);
829
838
  }
830
839
  textareaWrapper.appendChild(textareaInput);
831
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
840
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
832
841
  const counter = createCharCounter(element, textareaInput, true);
833
842
  textareaWrapper.appendChild(counter);
834
843
  }
@@ -836,6 +845,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
836
845
  }
837
846
  function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
838
847
  const state = ctx.state;
848
+ const readonly = isElementReadonly(element, state, ctx);
839
849
  const prefillValues = ctx.prefill[element.key] || [];
840
850
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
841
851
  const minCount = element.minCount ?? 1;
@@ -866,8 +876,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
866
876
  textareaInput.placeholder = element.placeholder || t("placeholderText", state);
867
877
  textareaInput.rows = element.rows || 4;
868
878
  textareaInput.value = value;
869
- textareaInput.readOnly = state.config.readonly;
870
- if (!state.config.readonly && ctx.instance) {
879
+ textareaInput.readOnly = readonly;
880
+ if (!readonly && ctx.instance) {
871
881
  const handleChange = () => {
872
882
  const value2 = textareaInput.value === "" ? null : textareaInput.value;
873
883
  ctx.instance.triggerOnChange(textareaInput.name, value2);
@@ -875,11 +885,11 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
875
885
  textareaInput.addEventListener("blur", handleChange);
876
886
  textareaInput.addEventListener("input", handleChange);
877
887
  }
878
- if (element.autoExpand || state.config.readonly) {
888
+ if (element.autoExpand || readonly) {
879
889
  applyAutoExpand(textareaInput);
880
890
  }
881
891
  textareaContainer.appendChild(textareaInput);
882
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
892
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
883
893
  const counter = createCharCounter(element, textareaInput, true);
884
894
  textareaContainer.appendChild(counter);
885
895
  }
@@ -893,7 +903,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
893
903
  return itemWrapper;
894
904
  }
895
905
  function updateRemoveButtons() {
896
- if (state.config.readonly) return;
906
+ if (readonly) return;
897
907
  const items = container.querySelectorAll(".multiple-textarea-item");
898
908
  const currentCount = items.length;
899
909
  items.forEach((item) => {
@@ -927,7 +937,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
927
937
  }
928
938
  let addRow = null;
929
939
  let countDisplay = null;
930
- if (!state.config.readonly) {
940
+ if (!readonly) {
931
941
  addRow = document.createElement("div");
932
942
  addRow.className = "flex items-center gap-3 mt-2";
933
943
  const addBtn = document.createElement("button");
@@ -961,7 +971,9 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
961
971
  }
962
972
  function updateAddButton() {
963
973
  if (!addRow || !countDisplay) return;
964
- const addBtn = addRow.querySelector(".add-textarea-btn");
974
+ const addBtn = addRow.querySelector(
975
+ ".add-textarea-btn"
976
+ );
965
977
  if (addBtn) {
966
978
  const disabled = values.length >= maxCount;
967
979
  addBtn.disabled = disabled;
@@ -980,7 +992,7 @@ function validateTextareaElement(element, key, context) {
980
992
  function updateTextareaField(element, fieldPath, value, context) {
981
993
  updateTextField(element, fieldPath, value, context);
982
994
  const { scopeRoot, state } = context;
983
- const shouldAutoExpand = element.autoExpand || state.config.readonly;
995
+ const shouldAutoExpand = element.autoExpand || isElementReadonly(element, state);
984
996
  if (!shouldAutoExpand) return;
985
997
  if (element.multiple) {
986
998
  const textareas = scopeRoot.querySelectorAll(
@@ -1041,6 +1053,7 @@ function createNumberRangeHint(element, input) {
1041
1053
  }
1042
1054
  function renderNumberElement(element, ctx, wrapper, pathKey) {
1043
1055
  const state = ctx.state;
1056
+ const readonly = isElementReadonly(element, state, ctx);
1044
1057
  const inputWrapper = document.createElement("div");
1045
1058
  inputWrapper.style.cssText = "position: relative;";
1046
1059
  const numberInput = document.createElement("input");
@@ -1053,8 +1066,8 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1053
1066
  if (element.max !== void 0) numberInput.max = element.max.toString();
1054
1067
  if (element.step !== void 0) numberInput.step = element.step.toString();
1055
1068
  numberInput.value = ctx.prefill[element.key] || element.default || "";
1056
- numberInput.readOnly = state.config.readonly;
1057
- if (!state.config.readonly && ctx.instance) {
1069
+ numberInput.readOnly = readonly;
1070
+ if (!readonly && ctx.instance) {
1058
1071
  const handleChange = () => {
1059
1072
  const value = numberInput.value ? parseFloat(numberInput.value) : null;
1060
1073
  ctx.instance.triggerOnChange(pathKey, value);
@@ -1063,7 +1076,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1063
1076
  numberInput.addEventListener("input", handleChange);
1064
1077
  }
1065
1078
  inputWrapper.appendChild(numberInput);
1066
- if (!state.config.readonly && (element.min != null || element.max != null)) {
1079
+ if (!readonly && (element.min != null || element.max != null)) {
1067
1080
  const counter = createNumberRangeHint(element, numberInput);
1068
1081
  inputWrapper.appendChild(counter);
1069
1082
  }
@@ -1071,6 +1084,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1071
1084
  }
1072
1085
  function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1073
1086
  const state = ctx.state;
1087
+ const readonly = isElementReadonly(element, state, ctx);
1074
1088
  const prefillValues = ctx.prefill[element.key] || [];
1075
1089
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
1076
1090
  const minCount = element.minCount ?? 1;
@@ -1104,8 +1118,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1104
1118
  if (element.max !== void 0) numberInput.max = element.max.toString();
1105
1119
  if (element.step !== void 0) numberInput.step = element.step.toString();
1106
1120
  numberInput.value = value.toString();
1107
- numberInput.readOnly = state.config.readonly;
1108
- if (!state.config.readonly && ctx.instance) {
1121
+ numberInput.readOnly = readonly;
1122
+ if (!readonly && ctx.instance) {
1109
1123
  const handleChange = () => {
1110
1124
  const val = numberInput.value ? parseFloat(numberInput.value) : null;
1111
1125
  ctx.instance.triggerOnChange(numberInput.name, val);
@@ -1114,7 +1128,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1114
1128
  numberInput.addEventListener("input", handleChange);
1115
1129
  }
1116
1130
  inputContainer.appendChild(numberInput);
1117
- if (!state.config.readonly && (element.min != null || element.max != null)) {
1131
+ if (!readonly && (element.min != null || element.max != null)) {
1118
1132
  const counter = createNumberRangeHint(element, numberInput);
1119
1133
  inputContainer.appendChild(counter);
1120
1134
  }
@@ -1128,7 +1142,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1128
1142
  return itemWrapper;
1129
1143
  }
1130
1144
  function updateRemoveButtons() {
1131
- if (state.config.readonly) return;
1145
+ if (readonly) return;
1132
1146
  const items = container.querySelectorAll(".multiple-number-item");
1133
1147
  const currentCount = items.length;
1134
1148
  items.forEach((item) => {
@@ -1162,7 +1176,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1162
1176
  }
1163
1177
  let addRow = null;
1164
1178
  let countDisplay = null;
1165
- if (!state.config.readonly) {
1179
+ if (!readonly) {
1166
1180
  addRow = document.createElement("div");
1167
1181
  addRow.className = "flex items-center gap-3 mt-2";
1168
1182
  const addBtn = document.createElement("button");
@@ -1367,10 +1381,11 @@ function updateNumberField(element, fieldPath, value, context) {
1367
1381
  // src/components/select.ts
1368
1382
  function renderSelectElement(element, ctx, wrapper, pathKey) {
1369
1383
  const state = ctx.state;
1384
+ const readonly = isElementReadonly(element, state, ctx);
1370
1385
  const selectInput = document.createElement("select");
1371
1386
  selectInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1372
1387
  selectInput.name = pathKey;
1373
- selectInput.disabled = state.config.readonly;
1388
+ selectInput.disabled = readonly;
1374
1389
  (element.options || []).forEach((option) => {
1375
1390
  const optionEl = document.createElement("option");
1376
1391
  optionEl.value = option.value;
@@ -1380,14 +1395,14 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1380
1395
  }
1381
1396
  selectInput.appendChild(optionEl);
1382
1397
  });
1383
- if (!state.config.readonly && ctx.instance) {
1398
+ if (!readonly && ctx.instance) {
1384
1399
  const handleChange = () => {
1385
1400
  ctx.instance.triggerOnChange(pathKey, selectInput.value);
1386
1401
  };
1387
1402
  selectInput.addEventListener("change", handleChange);
1388
1403
  }
1389
1404
  wrapper.appendChild(selectInput);
1390
- if (!state.config.readonly) {
1405
+ if (!readonly) {
1391
1406
  const selectHint = document.createElement("p");
1392
1407
  selectHint.className = "text-xs text-gray-500 mt-1";
1393
1408
  selectHint.textContent = makeFieldHint(element, state);
@@ -1396,6 +1411,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1396
1411
  }
1397
1412
  function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1398
1413
  const state = ctx.state;
1414
+ const readonly = isElementReadonly(element, state, ctx);
1399
1415
  const prefillValues = ctx.prefill[element.key] || [];
1400
1416
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
1401
1417
  const minCount = element.minCount ?? 1;
@@ -1420,7 +1436,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1420
1436
  itemWrapper.className = "multiple-select-item flex items-center gap-2";
1421
1437
  const selectInput = document.createElement("select");
1422
1438
  selectInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1423
- selectInput.disabled = state.config.readonly;
1439
+ selectInput.disabled = readonly;
1424
1440
  (element.options || []).forEach((option) => {
1425
1441
  const optionElement = document.createElement("option");
1426
1442
  optionElement.value = option.value;
@@ -1430,7 +1446,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1430
1446
  }
1431
1447
  selectInput.appendChild(optionElement);
1432
1448
  });
1433
- if (!state.config.readonly && ctx.instance) {
1449
+ if (!readonly && ctx.instance) {
1434
1450
  const handleChange = () => {
1435
1451
  ctx.instance.triggerOnChange(selectInput.name, selectInput.value);
1436
1452
  };
@@ -1446,7 +1462,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1446
1462
  return itemWrapper;
1447
1463
  }
1448
1464
  function updateRemoveButtons() {
1449
- if (state.config.readonly) return;
1465
+ if (readonly) return;
1450
1466
  const items = container.querySelectorAll(".multiple-select-item");
1451
1467
  const currentCount = items.length;
1452
1468
  items.forEach((item) => {
@@ -1478,7 +1494,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1478
1494
  }
1479
1495
  let addRow = null;
1480
1496
  let countDisplay = null;
1481
- if (!state.config.readonly) {
1497
+ if (!readonly) {
1482
1498
  addRow = document.createElement("div");
1483
1499
  addRow.className = "flex items-center gap-3 mt-2";
1484
1500
  const addBtn = document.createElement("button");
@@ -1525,7 +1541,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1525
1541
  values.forEach((value) => addSelectItem(value));
1526
1542
  updateAddButton();
1527
1543
  updateRemoveButtons();
1528
- if (!state.config.readonly) {
1544
+ if (!readonly) {
1529
1545
  const hint = document.createElement("p");
1530
1546
  hint.className = "text-xs text-gray-500 mt-1";
1531
1547
  hint.textContent = makeFieldHint(element, state);
@@ -1741,12 +1757,14 @@ function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onCha
1741
1757
  }
1742
1758
  function renderSwitcherElement(element, ctx, wrapper, pathKey) {
1743
1759
  const state = ctx.state;
1744
- const initialValue = String(ctx.prefill[element.key] ?? element.default ?? "");
1760
+ const initialValue = String(
1761
+ ctx.prefill[element.key] ?? element.default ?? ""
1762
+ );
1745
1763
  const hiddenInput = document.createElement("input");
1746
1764
  hiddenInput.type = "hidden";
1747
1765
  hiddenInput.name = pathKey;
1748
1766
  hiddenInput.value = initialValue;
1749
- const readonly = state.config.readonly;
1767
+ const readonly = isElementReadonly(element, state, ctx);
1750
1768
  const onChange = !readonly && ctx.instance ? (value) => {
1751
1769
  ctx.instance.triggerOnChange(pathKey, value);
1752
1770
  } : null;
@@ -1775,7 +1793,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
1775
1793
  while (values.length < minCount) {
1776
1794
  values.push(element.default || element.options?.[0]?.value || "");
1777
1795
  }
1778
- const readonly = state.config.readonly;
1796
+ const readonly = isElementReadonly(element, state, ctx);
1779
1797
  const container = document.createElement("div");
1780
1798
  container.className = "space-y-2";
1781
1799
  wrapper.appendChild(container);
@@ -2178,9 +2196,7 @@ function renderUploadedVideoPreview(container, thumbnailUrl, _videoType, state)
2178
2196
  video.preload = "metadata";
2179
2197
  video.muted = true;
2180
2198
  video.src = thumbnailUrl;
2181
- video.appendChild(
2182
- document.createTextNode(t("videoNotSupported", state))
2183
- );
2199
+ video.appendChild(document.createTextNode(t("videoNotSupported", state)));
2184
2200
  container.appendChild(video);
2185
2201
  }
2186
2202
  function renderDeleteButton(container, resourceId, state) {
@@ -2288,6 +2304,10 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
2288
2304
  meta,
2289
2305
  state
2290
2306
  );
2307
+ const isVideo = meta?.type?.startsWith("video/");
2308
+ if (!isReadonly && !isVideo) {
2309
+ renderDeleteButton(container, resourceId, state);
2310
+ }
2291
2311
  }
2292
2312
  }
2293
2313
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -2988,9 +3008,15 @@ function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallbac
2988
3008
  }
2989
3009
  function renderFileElement(element, ctx, wrapper, pathKey) {
2990
3010
  const state = ctx.state;
2991
- if (state.config.readonly) {
2992
- const initial = ctx.prefill[element.key];
3011
+ if (isElementReadonly(element, state, ctx)) {
3012
+ const rawInitial = ctx.prefill[element.key];
3013
+ const initial = typeof rawInitial === "string" ? rawInitial : "";
2993
3014
  if (initial) {
3015
+ const hiddenInput = document.createElement("input");
3016
+ hiddenInput.type = "hidden";
3017
+ hiddenInput.name = pathKey;
3018
+ hiddenInput.value = initial;
3019
+ wrapper.appendChild(hiddenInput);
2994
3020
  renderFilePreviewReadonly(initial, state).then((filePreview) => {
2995
3021
  wrapper.appendChild(filePreview);
2996
3022
  }).catch((err) => {
@@ -3078,10 +3104,24 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
3078
3104
  }
3079
3105
  function renderFilesElement(element, ctx, wrapper, pathKey) {
3080
3106
  const state = ctx.state;
3081
- if (state.config.readonly) {
3107
+ if (isElementReadonly(element, state, ctx)) {
3108
+ const rawPrefill = ctx.prefill[element.key];
3109
+ const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
3110
+ const filesWrapper = document.createElement("div");
3111
+ filesWrapper.className = "space-y-2";
3112
+ filesWrapper.dataset.filesWrapper = pathKey;
3113
+ const filesList = document.createElement("div");
3114
+ filesList.className = "files-list";
3115
+ initialFiles.forEach((resourceId) => {
3116
+ const pill = document.createElement("div");
3117
+ pill.className = "resource-pill";
3118
+ pill.dataset.resourceId = resourceId;
3119
+ filesList.appendChild(pill);
3120
+ });
3121
+ filesWrapper.appendChild(filesList);
3122
+ wrapper.appendChild(filesWrapper);
3082
3123
  const resultsWrapper = document.createElement("div");
3083
3124
  resultsWrapper.className = "space-y-4";
3084
- const initialFiles = ctx.prefill[element.key] || [];
3085
3125
  if (initialFiles.length > 0) {
3086
3126
  initialFiles.forEach((resourceId) => {
3087
3127
  renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
@@ -3112,6 +3152,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
3112
3152
  };
3113
3153
  const filesWrapper = document.createElement("div");
3114
3154
  filesWrapper.className = "space-y-2";
3155
+ filesWrapper.dataset.filesWrapper = pathKey;
3115
3156
  const filesPicker = document.createElement("input");
3116
3157
  filesPicker.type = "file";
3117
3158
  filesPicker.name = pathKey;
@@ -3161,10 +3202,24 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
3161
3202
  const state = ctx.state;
3162
3203
  const minFiles = element.minCount ?? 0;
3163
3204
  const maxFiles = element.maxCount ?? Infinity;
3164
- if (state.config.readonly) {
3205
+ if (isElementReadonly(element, state, ctx)) {
3206
+ const rawPrefill = ctx.prefill[element.key];
3207
+ const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
3208
+ const filesWrapper = document.createElement("div");
3209
+ filesWrapper.className = "space-y-2";
3210
+ filesWrapper.dataset.filesWrapper = pathKey;
3211
+ const filesList = document.createElement("div");
3212
+ filesList.className = "files-list";
3213
+ initialFiles.forEach((resourceId) => {
3214
+ const pill = document.createElement("div");
3215
+ pill.className = "resource-pill";
3216
+ pill.dataset.resourceId = resourceId;
3217
+ filesList.appendChild(pill);
3218
+ });
3219
+ filesWrapper.appendChild(filesList);
3220
+ wrapper.appendChild(filesWrapper);
3165
3221
  const resultsWrapper = document.createElement("div");
3166
3222
  resultsWrapper.className = "space-y-4";
3167
- const initialFiles = ctx.prefill[element.key] || [];
3168
3223
  if (initialFiles.length > 0) {
3169
3224
  initialFiles.forEach((resourceId) => {
3170
3225
  renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
@@ -3180,6 +3235,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
3180
3235
  } else {
3181
3236
  const filesWrapper = document.createElement("div");
3182
3237
  filesWrapper.className = "space-y-2";
3238
+ filesWrapper.dataset.filesWrapper = pathKey;
3183
3239
  const filesPicker = document.createElement("input");
3184
3240
  filesPicker.type = "file";
3185
3241
  filesPicker.name = pathKey;
@@ -3293,10 +3349,9 @@ function validateFileElement(element, key, context) {
3293
3349
  };
3294
3350
  if (isMultipleField) {
3295
3351
  const fullKey = pathJoin(path, key);
3296
- const pickerInput = scopeRoot.querySelector(
3297
- `input[type="file"][name="${fullKey}"]`
3352
+ const filesWrapper = scopeRoot.querySelector(
3353
+ `[data-files-wrapper="${fullKey}"]`
3298
3354
  );
3299
- const filesWrapper = pickerInput?.closest(".space-y-2");
3300
3355
  const container = filesWrapper?.querySelector(".files-list") || null;
3301
3356
  const resourceIds = [];
3302
3357
  if (container) {
@@ -3555,15 +3610,16 @@ function createEditColourUI(value, pathKey, ctx) {
3555
3610
  }
3556
3611
  function renderColourElement(element, ctx, wrapper, pathKey) {
3557
3612
  const state = ctx.state;
3613
+ const readonly = isElementReadonly(element, state, ctx);
3558
3614
  const initialValue = ctx.prefill[element.key] || element.default || "#000000";
3559
- if (state.config.readonly) {
3615
+ if (readonly) {
3560
3616
  const readonlyUI = createReadonlyColourUI(initialValue);
3561
3617
  wrapper.appendChild(readonlyUI);
3562
3618
  } else {
3563
3619
  const editUI = createEditColourUI(initialValue, pathKey, ctx);
3564
3620
  wrapper.appendChild(editUI);
3565
3621
  }
3566
- if (!state.config.readonly) {
3622
+ if (!readonly) {
3567
3623
  const colourHint = document.createElement("p");
3568
3624
  colourHint.className = "mt-1";
3569
3625
  colourHint.style.cssText = `
@@ -3576,6 +3632,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
3576
3632
  }
3577
3633
  function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3578
3634
  const state = ctx.state;
3635
+ const readonly = isElementReadonly(element, state, ctx);
3579
3636
  const prefillValues = ctx.prefill[element.key] || [];
3580
3637
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
3581
3638
  const minCount = element.minCount ?? 1;
@@ -3598,7 +3655,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3598
3655
  function addColourItem(value = "#000000", index = -1) {
3599
3656
  const itemWrapper = document.createElement("div");
3600
3657
  itemWrapper.className = "multiple-colour-item flex items-center gap-2";
3601
- if (state.config.readonly) {
3658
+ if (readonly) {
3602
3659
  const readonlyUI = createReadonlyColourUI(value);
3603
3660
  while (readonlyUI.firstChild) {
3604
3661
  itemWrapper.appendChild(readonlyUI.firstChild);
@@ -3618,7 +3675,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3618
3675
  return itemWrapper;
3619
3676
  }
3620
3677
  function updateRemoveButtons() {
3621
- if (state.config.readonly) return;
3678
+ if (readonly) return;
3622
3679
  const items = container.querySelectorAll(".multiple-colour-item");
3623
3680
  const currentCount = items.length;
3624
3681
  items.forEach((item) => {
@@ -3663,7 +3720,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3663
3720
  }
3664
3721
  let addRow = null;
3665
3722
  let countDisplay = null;
3666
- if (!state.config.readonly) {
3723
+ if (!readonly) {
3667
3724
  addRow = document.createElement("div");
3668
3725
  addRow.className = "flex items-center gap-3 mt-2";
3669
3726
  const addBtn = document.createElement("button");
@@ -3710,7 +3767,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3710
3767
  values.forEach((value) => addColourItem(value));
3711
3768
  updateAddButton();
3712
3769
  updateRemoveButtons();
3713
- if (!state.config.readonly) {
3770
+ if (!readonly) {
3714
3771
  const hint = document.createElement("p");
3715
3772
  hint.className = "mt-1";
3716
3773
  hint.style.cssText = `
@@ -3779,7 +3836,9 @@ function validateColourElement(element, key, context) {
3779
3836
  return normalized;
3780
3837
  };
3781
3838
  if (element.multiple) {
3782
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${key}["].colour-hex-input`);
3839
+ const hexInputs = scopeRoot.querySelectorAll(
3840
+ `[name^="${key}["].colour-hex-input`
3841
+ );
3783
3842
  const values = [];
3784
3843
  hexInputs.forEach((input, index) => {
3785
3844
  const val = input?.value ?? "";
@@ -3826,7 +3885,9 @@ function updateColourField(element, fieldPath, value, context) {
3826
3885
  );
3827
3886
  return;
3828
3887
  }
3829
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["].colour-hex-input`);
3888
+ const hexInputs = scopeRoot.querySelectorAll(
3889
+ `[name^="${fieldPath}["].colour-hex-input`
3890
+ );
3830
3891
  hexInputs.forEach((hexInput, index) => {
3831
3892
  if (index < value.length) {
3832
3893
  const normalized = normalizeColourValue(value[index]);
@@ -4021,6 +4082,7 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4021
4082
  );
4022
4083
  }
4023
4084
  const state = ctx.state;
4085
+ const readonly = isElementReadonly(element, state, ctx);
4024
4086
  const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
4025
4087
  const initialValue = ctx.prefill[element.key] ?? defaultValue;
4026
4088
  const sliderUI = createSliderUI(
@@ -4028,10 +4090,10 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4028
4090
  pathKey,
4029
4091
  element,
4030
4092
  ctx,
4031
- state.config.readonly
4093
+ readonly
4032
4094
  );
4033
4095
  wrapper.appendChild(sliderUI);
4034
- if (!state.config.readonly) {
4096
+ if (!readonly) {
4035
4097
  const hint = document.createElement("p");
4036
4098
  hint.className = "mt-1";
4037
4099
  hint.style.cssText = `
@@ -4055,6 +4117,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4055
4117
  );
4056
4118
  }
4057
4119
  const state = ctx.state;
4120
+ const readonly = isElementReadonly(element, state, ctx);
4058
4121
  const prefillValues = ctx.prefill[element.key] || [];
4059
4122
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
4060
4123
  const minCount = element.minCount ?? 1;
@@ -4079,13 +4142,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4079
4142
  const itemWrapper = document.createElement("div");
4080
4143
  itemWrapper.className = "multiple-slider-item flex items-start gap-2";
4081
4144
  const tempPathKey = `${pathKey}[${container.children.length}]`;
4082
- const sliderUI = createSliderUI(
4083
- value,
4084
- tempPathKey,
4085
- element,
4086
- ctx,
4087
- state.config.readonly
4088
- );
4145
+ const sliderUI = createSliderUI(value, tempPathKey, element, ctx, readonly);
4089
4146
  sliderUI.style.flex = "1";
4090
4147
  itemWrapper.appendChild(sliderUI);
4091
4148
  if (index === -1) {
@@ -4097,7 +4154,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4097
4154
  return itemWrapper;
4098
4155
  }
4099
4156
  function updateRemoveButtons() {
4100
- if (state.config.readonly) return;
4157
+ if (readonly) return;
4101
4158
  const items = container.querySelectorAll(".multiple-slider-item");
4102
4159
  const currentCount = items.length;
4103
4160
  items.forEach((item) => {
@@ -4143,7 +4200,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4143
4200
  }
4144
4201
  let addRow = null;
4145
4202
  let countDisplay = null;
4146
- if (!state.config.readonly) {
4203
+ if (!readonly) {
4147
4204
  addRow = document.createElement("div");
4148
4205
  addRow.className = "flex items-center gap-3 mt-2";
4149
4206
  const addBtn = document.createElement("button");
@@ -4189,7 +4246,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4189
4246
  values.forEach((value) => addSliderItem(value));
4190
4247
  updateAddButton();
4191
4248
  updateRemoveButtons();
4192
- if (!state.config.readonly) {
4249
+ if (!readonly) {
4193
4250
  const hint = document.createElement("p");
4194
4251
  hint.className = "mt-1";
4195
4252
  hint.style.cssText = `
@@ -4428,9 +4485,7 @@ function mergeWithDefaults(prefill, defaults) {
4428
4485
  }
4429
4486
  function extractRootFormData(formRoot) {
4430
4487
  const data = {};
4431
- const inputs = formRoot.querySelectorAll(
4432
- "input, select, textarea"
4433
- );
4488
+ const inputs = formRoot.querySelectorAll("input, select, textarea");
4434
4489
  inputs.forEach((input) => {
4435
4490
  const fieldName = input.getAttribute("name");
4436
4491
  if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
@@ -4494,7 +4549,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4494
4549
  } else {
4495
4550
  itemsWrap.className = `grid grid-cols-${columns} gap-4`;
4496
4551
  }
4497
- if (!ctx.state.config.readonly) {
4552
+ const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
4553
+ if (!containerIsReadonly) {
4498
4554
  const hintsElement = createPrefillHints(element, pathKey);
4499
4555
  if (hintsElement) {
4500
4556
  containerWrap.appendChild(hintsElement);
@@ -4509,12 +4565,15 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4509
4565
  // Merged prefill with defaults for enableIf evaluation
4510
4566
  formData: ctx.formData ?? ctx.prefill,
4511
4567
  // Complete root data for enableIf evaluation
4512
- state: ctx.state
4568
+ state: ctx.state,
4569
+ inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
4513
4570
  };
4514
4571
  element.elements.forEach((child) => {
4515
4572
  if (child.hidden || child.type === "hidden") {
4516
4573
  const prefillVal = containerPrefill[child.key] ?? child.default ?? null;
4517
- itemsWrap.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
4574
+ itemsWrap.appendChild(
4575
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
4576
+ );
4518
4577
  } else {
4519
4578
  itemsWrap.appendChild(renderElement(child, subCtx));
4520
4579
  }
@@ -4524,13 +4583,15 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4524
4583
  }
4525
4584
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4526
4585
  const state = ctx.state;
4586
+ const containerIsReadonly = isElementReadonly(element, state, ctx);
4587
+ const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
4527
4588
  const containerWrap = document.createElement("div");
4528
4589
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
4529
4590
  const countDisplay = document.createElement("span");
4530
4591
  countDisplay.className = "text-sm text-gray-500";
4531
4592
  const itemsWrap = document.createElement("div");
4532
4593
  itemsWrap.className = "space-y-4";
4533
- if (!ctx.state.config.readonly) {
4594
+ if (!containerIsReadonly) {
4534
4595
  const hintsElement = createPrefillHints(element, element.key);
4535
4596
  if (hintsElement) {
4536
4597
  containerWrap.appendChild(hintsElement);
@@ -4568,8 +4629,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4568
4629
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4569
4630
  prefill: childDefaults,
4570
4631
  // Defaults for enableIf evaluation
4571
- formData: currentFormData
4632
+ formData: currentFormData,
4572
4633
  // Current root data from DOM for enableIf
4634
+ inheritedReadonly: childInheritedReadonly
4573
4635
  };
4574
4636
  const item = document.createElement("div");
4575
4637
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4583,13 +4645,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4583
4645
  }
4584
4646
  element.elements.forEach((child) => {
4585
4647
  if (child.hidden || child.type === "hidden") {
4586
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), child.default ?? null));
4648
+ childWrapper.appendChild(
4649
+ createHiddenInput(
4650
+ pathJoin(subCtx.path, child.key),
4651
+ child.default ?? null
4652
+ )
4653
+ );
4587
4654
  } else {
4588
4655
  childWrapper.appendChild(renderElement(child, subCtx));
4589
4656
  }
4590
4657
  });
4591
4658
  item.appendChild(childWrapper);
4592
- if (!state.config.readonly) {
4659
+ if (!containerIsReadonly) {
4593
4660
  const rem = document.createElement("button");
4594
4661
  rem.type = "button";
4595
4662
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4639,8 +4706,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4639
4706
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4640
4707
  prefill: mergedPrefill,
4641
4708
  // Merged prefill with defaults for enableIf
4642
- formData: ctx.formData ?? ctx.prefill
4709
+ formData: ctx.formData ?? ctx.prefill,
4643
4710
  // Complete root data for enableIf
4711
+ inheritedReadonly: childInheritedReadonly
4644
4712
  };
4645
4713
  const item = document.createElement("div");
4646
4714
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4655,13 +4723,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4655
4723
  element.elements.forEach((child) => {
4656
4724
  if (child.hidden || child.type === "hidden") {
4657
4725
  const prefillVal = prefillObj?.[child.key] ?? child.default ?? null;
4658
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
4726
+ childWrapper.appendChild(
4727
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
4728
+ );
4659
4729
  } else {
4660
4730
  childWrapper.appendChild(renderElement(child, subCtx));
4661
4731
  }
4662
4732
  });
4663
4733
  item.appendChild(childWrapper);
4664
- if (!state.config.readonly) {
4734
+ if (!containerIsReadonly) {
4665
4735
  const rem = document.createElement("button");
4666
4736
  rem.type = "button";
4667
4737
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4684,7 +4754,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4684
4754
  itemsWrap.appendChild(item);
4685
4755
  });
4686
4756
  }
4687
- if (!state.config.readonly) {
4757
+ if (!containerIsReadonly) {
4688
4758
  while (countItems() < min) {
4689
4759
  const idx = countItems();
4690
4760
  const subCtx = {
@@ -4692,8 +4762,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4692
4762
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4693
4763
  prefill: childDefaults,
4694
4764
  // Defaults for enableIf evaluation
4695
- formData: ctx.formData ?? ctx.prefill
4765
+ formData: ctx.formData ?? ctx.prefill,
4696
4766
  // Complete root data for enableIf
4767
+ inheritedReadonly: childInheritedReadonly
4697
4768
  };
4698
4769
  const item = document.createElement("div");
4699
4770
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4707,7 +4778,12 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4707
4778
  }
4708
4779
  element.elements.forEach((child) => {
4709
4780
  if (child.hidden || child.type === "hidden") {
4710
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), child.default ?? null));
4781
+ childWrapper.appendChild(
4782
+ createHiddenInput(
4783
+ pathJoin(subCtx.path, child.key),
4784
+ child.default ?? null
4785
+ )
4786
+ );
4711
4787
  } else {
4712
4788
  childWrapper.appendChild(renderElement(child, subCtx));
4713
4789
  }
@@ -4739,7 +4815,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4739
4815
  }
4740
4816
  }
4741
4817
  containerWrap.appendChild(itemsWrap);
4742
- if (!state.config.readonly) {
4818
+ if (!containerIsReadonly) {
4743
4819
  const addRow = document.createElement("div");
4744
4820
  addRow.className = "flex items-center gap-3 mt-2";
4745
4821
  addRow.appendChild(createAddButton());
@@ -4898,8 +4974,10 @@ function updateContainerField(element, fieldPath, value, context) {
4898
4974
  const filesKey = richChild.filesKey ?? "files";
4899
4975
  const containerValue = itemValue;
4900
4976
  const compositeValue = {};
4901
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
4902
- if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
4977
+ if (textKey in containerValue)
4978
+ compositeValue[textKey] = containerValue[textKey];
4979
+ if (filesKey in containerValue)
4980
+ compositeValue[filesKey] = containerValue[filesKey];
4903
4981
  if (Object.keys(compositeValue).length > 0) {
4904
4982
  instance.updateField(childPath, compositeValue);
4905
4983
  }
@@ -4935,8 +5013,10 @@ function updateContainerField(element, fieldPath, value, context) {
4935
5013
  const filesKey = richChild.filesKey ?? "files";
4936
5014
  const containerValue = value;
4937
5015
  const compositeValue = {};
4938
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
4939
- if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
5016
+ if (textKey in containerValue)
5017
+ compositeValue[textKey] = containerValue[textKey];
5018
+ if (filesKey in containerValue)
5019
+ compositeValue[filesKey] = containerValue[filesKey];
4940
5020
  if (Object.keys(compositeValue).length > 0) {
4941
5021
  instance.updateField(childPath, compositeValue);
4942
5022
  }
@@ -5274,12 +5354,21 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5274
5354
  const hiddenInput = document.createElement("input");
5275
5355
  hiddenInput.type = "hidden";
5276
5356
  hiddenInput.name = pathKey;
5277
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
5357
+ hiddenInput.value = JSON.stringify({
5358
+ [cellsKey]: cells,
5359
+ [mergesKey]: merges
5360
+ });
5278
5361
  wrapper.appendChild(hiddenInput);
5279
5362
  function persistValue() {
5280
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
5363
+ hiddenInput.value = JSON.stringify({
5364
+ [cellsKey]: cells,
5365
+ [mergesKey]: merges
5366
+ });
5281
5367
  if (instance) {
5282
- instance.triggerOnChange(pathKey, { [cellsKey]: cells, [mergesKey]: merges });
5368
+ instance.triggerOnChange(pathKey, {
5369
+ [cellsKey]: cells,
5370
+ [mergesKey]: merges
5371
+ });
5283
5372
  }
5284
5373
  }
5285
5374
  hiddenInput._applyExternalUpdate = (data) => {
@@ -5338,9 +5427,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5338
5427
  rebuild();
5339
5428
  } catch (e) {
5340
5429
  const errMsg = e instanceof Error ? e.message : String(e);
5341
- console.error(
5342
- t("tableImportError", state).replace("{error}", errMsg)
5343
- );
5430
+ console.error(t("tableImportError", state).replace("{error}", errMsg));
5344
5431
  } finally {
5345
5432
  overlay.remove();
5346
5433
  }
@@ -5488,15 +5575,19 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5488
5575
  contextMenu.style.display = "none";
5489
5576
  }
5490
5577
  const menuDismissCtrl = new AbortController();
5491
- document.addEventListener("mousedown", (e) => {
5492
- if (!wrapper.isConnected) {
5493
- menuDismissCtrl.abort();
5494
- return;
5495
- }
5496
- if (!contextMenu.contains(e.target)) {
5497
- hideContextMenu();
5498
- }
5499
- }, { signal: menuDismissCtrl.signal });
5578
+ document.addEventListener(
5579
+ "mousedown",
5580
+ (e) => {
5581
+ if (!wrapper.isConnected) {
5582
+ menuDismissCtrl.abort();
5583
+ return;
5584
+ }
5585
+ if (!contextMenu.contains(e.target)) {
5586
+ hideContextMenu();
5587
+ }
5588
+ },
5589
+ { signal: menuDismissCtrl.signal }
5590
+ );
5500
5591
  function applySelectionStyles() {
5501
5592
  const range = selectionRange(sel);
5502
5593
  const allTds = tableEl.querySelectorAll("td[data-row]");
@@ -5842,7 +5933,9 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5842
5933
  if (!anchor) return;
5843
5934
  const text = e.clipboardData?.getData("text/plain") ?? "";
5844
5935
  const isMultiCell = text.includes(" ") || text.split(/\r?\n/).filter((l) => l).length > 1;
5845
- const editing = tableEl.querySelector("[contenteditable='true']");
5936
+ const editing = tableEl.querySelector(
5937
+ "[contenteditable='true']"
5938
+ );
5846
5939
  if (editing && !isMultiCell) return;
5847
5940
  e.preventDefault();
5848
5941
  if (editing) {
@@ -5998,7 +6091,11 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5998
6091
  }
5999
6092
  }
6000
6093
  function updateTopZoneOverlays(mx, wr, tblR, scrollL, active) {
6001
- const headerCells = active ? Array.from(tableEl.querySelectorAll("thead td[data-col]")) : [];
6094
+ const headerCells = active ? Array.from(
6095
+ tableEl.querySelectorAll(
6096
+ "thead td[data-col]"
6097
+ )
6098
+ ) : [];
6002
6099
  let closestColIdx = -1;
6003
6100
  let closestColDist = Infinity;
6004
6101
  let closestBorderX = -1;
@@ -6016,7 +6113,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6016
6113
  if (borderDist < closestBorderDist && borderDist < 20) {
6017
6114
  closestBorderDist = borderDist;
6018
6115
  closestBorderX = cellRect.right - wr.left + scrollL;
6019
- closestAfterCol = parseInt(headerCells[i].getAttribute("data-col") ?? "0", 10);
6116
+ closestAfterCol = parseInt(
6117
+ headerCells[i].getAttribute("data-col") ?? "0",
6118
+ 10
6119
+ );
6020
6120
  }
6021
6121
  }
6022
6122
  colRemoveBtns.forEach((btn, idx) => {
@@ -6071,7 +6171,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6071
6171
  closestRowBorderDist = borderDist;
6072
6172
  closestBorderY = trRect.bottom - wr.top;
6073
6173
  const firstTd = allRowEls[i].querySelector("td[data-row]");
6074
- closestAfterRow = parseInt(firstTd?.getAttribute("data-row") ?? "0", 10);
6174
+ closestAfterRow = parseInt(
6175
+ firstTd?.getAttribute("data-row") ?? "0",
6176
+ 10
6177
+ );
6075
6178
  }
6076
6179
  }
6077
6180
  rowRemoveBtns.forEach((btn, idx) => {
@@ -6097,7 +6200,8 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6097
6200
  let rafPending = false;
6098
6201
  tableWrapper.onmousemove = (e) => {
6099
6202
  const target = e.target;
6100
- if (target.tagName === "BUTTON" && target.parentElement === tableWrapper) return;
6203
+ if (target.tagName === "BUTTON" && target.parentElement === tableWrapper)
6204
+ return;
6101
6205
  if (rafPending) return;
6102
6206
  rafPending = true;
6103
6207
  const mx = e.clientX;
@@ -6156,6 +6260,7 @@ function isTableDataWithFieldNames(v, cellsKey) {
6156
6260
  }
6157
6261
  function renderTableElement(element, ctx, wrapper, pathKey) {
6158
6262
  const state = ctx.state;
6263
+ const readonly = isElementReadonly(element, state, ctx);
6159
6264
  const rawPrefill = ctx.prefill[element.key];
6160
6265
  const cellsKey = element.fieldNames?.cells ?? "cells";
6161
6266
  const mergesKey = element.fieldNames?.merges ?? "merges";
@@ -6190,11 +6295,13 @@ function renderTableElement(element, ctx, wrapper, pathKey) {
6190
6295
  const cols = rows > 0 ? initialData.cells[0].length : 0;
6191
6296
  const err = validateMerges(initialData.merges, rows, cols);
6192
6297
  if (err) {
6193
- console.warn(`Table "${element.key}": invalid prefill merges stripped (${err})`);
6298
+ console.warn(
6299
+ `Table "${element.key}": invalid prefill merges stripped (${err})`
6300
+ );
6194
6301
  initialData = { ...initialData, merges: [] };
6195
6302
  }
6196
6303
  }
6197
- if (state.config.readonly) {
6304
+ if (readonly) {
6198
6305
  renderReadonlyTable(initialData, wrapper);
6199
6306
  } else {
6200
6307
  renderEditTable(element, initialData, pathKey, ctx, wrapper);
@@ -6871,20 +6978,26 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
6871
6978
  });
6872
6979
  let mentionTooltip = null;
6873
6980
  backdrop.addEventListener("mouseover", (e) => {
6874
- const mark = e.target.closest?.("mark");
6981
+ const mark = e.target.closest?.(
6982
+ "mark"
6983
+ );
6875
6984
  if (!mark?.dataset.rid) return;
6876
6985
  mentionTooltip = removePortalTooltip(mentionTooltip);
6877
6986
  mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
6878
6987
  });
6879
6988
  backdrop.addEventListener("mouseout", (e) => {
6880
- const mark = e.target.closest?.("mark");
6989
+ const mark = e.target.closest?.(
6990
+ "mark"
6991
+ );
6881
6992
  if (!mark) return;
6882
6993
  const related = e.relatedTarget;
6883
6994
  if (related?.closest?.("mark")) return;
6884
6995
  mentionTooltip = removePortalTooltip(mentionTooltip);
6885
6996
  });
6886
6997
  backdrop.addEventListener("mousedown", (e) => {
6887
- const mark = e.target.closest?.("mark");
6998
+ const mark = e.target.closest?.(
6999
+ "mark"
7000
+ );
6888
7001
  if (!mark) return;
6889
7002
  mentionTooltip = removePortalTooltip(mentionTooltip);
6890
7003
  const marks = backdrop.querySelectorAll("mark");
@@ -7128,11 +7241,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7128
7241
  textarea.addEventListener("keydown", (e) => {
7129
7242
  if (!dropdownState.open) return;
7130
7243
  const labels = buildFileLabelsFromClosure();
7131
- const filtered = filterFilesForDropdown(
7132
- dropdownState.query,
7133
- files,
7134
- labels
7135
- );
7244
+ const filtered = filterFilesForDropdown(dropdownState.query, files, labels);
7136
7245
  if (e.key === "ArrowDown") {
7137
7246
  e.preventDefault();
7138
7247
  dropdownState.selectedIndex = Math.min(
@@ -7459,6 +7568,7 @@ function renderReadonlyMode(_element, ctx, wrapper, _pathKey, value) {
7459
7568
  }
7460
7569
  function renderRichInputElement(element, ctx, wrapper, pathKey) {
7461
7570
  const state = ctx.state;
7571
+ const readonly = isElementReadonly(element, state, ctx);
7462
7572
  const textKey = element.textKey ?? "text";
7463
7573
  const filesKey = element.filesKey ?? "files";
7464
7574
  let initialValue;
@@ -7496,7 +7606,7 @@ function renderRichInputElement(element, ctx, wrapper, pathKey) {
7496
7606
  });
7497
7607
  }
7498
7608
  }
7499
- if (state.config.readonly) {
7609
+ if (readonly) {
7500
7610
  renderReadonlyMode(element, ctx, wrapper, pathKey, initialValue);
7501
7611
  } else {
7502
7612
  if (!state.config.uploadFile) {
@@ -7558,9 +7668,7 @@ function validateRichInputElement(element, key, context) {
7558
7668
  }
7559
7669
  }
7560
7670
  if (element.maxFiles != null && files.length > element.maxFiles) {
7561
- errors.push(
7562
- `${key}: ${t("maxFiles", state, { max: element.maxFiles })}`
7563
- );
7671
+ errors.push(`${key}: ${t("maxFiles", state, { max: element.maxFiles })}`);
7564
7672
  }
7565
7673
  }
7566
7674
  return { value, errors, spread: !!element.flatOutput };
@@ -7676,9 +7784,7 @@ function shouldDisableElement(element, ctx) {
7676
7784
  return false;
7677
7785
  }
7678
7786
  function extractDOMValue(fieldPath, formRoot) {
7679
- const input = formRoot.querySelector(
7680
- `[name="${fieldPath}"]`
7681
- );
7787
+ const input = formRoot.querySelector(`[name="${fieldPath}"]`);
7682
7788
  if (!input) {
7683
7789
  return void 0;
7684
7790
  }
@@ -7723,9 +7829,7 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7723
7829
  `[data-container-item="${containerKey}[${containerIndex}]"]`
7724
7830
  );
7725
7831
  if (containerItemElement) {
7726
- const inputs = containerItemElement.querySelectorAll(
7727
- "input, select, textarea"
7728
- );
7832
+ const inputs = containerItemElement.querySelectorAll("input, select, textarea");
7729
7833
  inputs.forEach((input) => {
7730
7834
  const fieldName = input.getAttribute("name");
7731
7835
  if (fieldName) {
@@ -7777,7 +7881,10 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7777
7881
  wrapper.setAttribute("data-conditionally-disabled", "true");
7778
7882
  }
7779
7883
  } catch (error) {
7780
- console.error(`Error re-evaluating enableIf for field "${element.key}":`, error);
7884
+ console.error(
7885
+ `Error re-evaluating enableIf for field "${element.key}":`,
7886
+ error
7887
+ );
7781
7888
  }
7782
7889
  }
7783
7890
  function setupEnableIfListeners(wrapper, element, ctx) {
@@ -7798,14 +7905,10 @@ function setupEnableIfListeners(wrapper, element, ctx) {
7798
7905
  } else {
7799
7906
  dependencyFieldPath = dependencyKey;
7800
7907
  }
7801
- const dependencyInput = formRoot.querySelector(
7802
- `[name="${dependencyFieldPath}"]`
7803
- );
7908
+ const dependencyInput = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7804
7909
  if (!dependencyInput) {
7805
7910
  const observer = new MutationObserver(() => {
7806
- const input = formRoot.querySelector(
7807
- `[name="${dependencyFieldPath}"]`
7808
- );
7911
+ const input = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7809
7912
  if (input) {
7810
7913
  input.addEventListener("change", () => {
7811
7914
  reevaluateEnableIf(wrapper, element, ctx);
@@ -7952,7 +8055,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
7952
8055
  default: {
7953
8056
  const unsupported = document.createElement("div");
7954
8057
  unsupported.className = "text-red-500 text-sm";
7955
- unsupported.textContent = t("unsupportedFieldType", ctx.state, { type: element.type });
8058
+ unsupported.textContent = t("unsupportedFieldType", ctx.state, {
8059
+ type: element.type
8060
+ });
7956
8061
  wrapper.appendChild(unsupported);
7957
8062
  }
7958
8063
  }
@@ -8904,7 +9009,10 @@ var FormBuilderInstance = class {
8904
9009
  );
8905
9010
  if (componentResult !== null) {
8906
9011
  errors.push(...componentResult.errors);
8907
- return { value: componentResult.value, spread: !!componentResult.spread };
9012
+ return {
9013
+ value: componentResult.value,
9014
+ spread: !!componentResult.spread
9015
+ };
8908
9016
  }
8909
9017
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
8910
9018
  return { value: null, spread: false };
@@ -8913,10 +9021,7 @@ var FormBuilderInstance = class {
8913
9021
  this.state.schema.elements.forEach((element) => {
8914
9022
  if (element.enableIf) {
8915
9023
  try {
8916
- const shouldEnable = evaluateEnableCondition(
8917
- element.enableIf,
8918
- data
8919
- );
9024
+ const shouldEnable = evaluateEnableCondition(element.enableIf, data);
8920
9025
  if (!shouldEnable) {
8921
9026
  return;
8922
9027
  }
@@ -9211,7 +9316,10 @@ var FormBuilderInstance = class {
9211
9316
  disabledWrapper.className = "fb-field-wrapper-disabled";
9212
9317
  disabledWrapper.style.display = "none";
9213
9318
  disabledWrapper.setAttribute("data-field-key", element.key);
9214
- disabledWrapper.setAttribute("data-conditionally-disabled", "true");
9319
+ disabledWrapper.setAttribute(
9320
+ "data-conditionally-disabled",
9321
+ "true"
9322
+ );
9215
9323
  wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
9216
9324
  }
9217
9325
  } catch (error) {