@dmitryvim/form-builder 0.2.25 → 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) {
@@ -2992,9 +3008,15 @@ function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallbac
2992
3008
  }
2993
3009
  function renderFileElement(element, ctx, wrapper, pathKey) {
2994
3010
  const state = ctx.state;
2995
- if (state.config.readonly) {
2996
- 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 : "";
2997
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);
2998
3020
  renderFilePreviewReadonly(initial, state).then((filePreview) => {
2999
3021
  wrapper.appendChild(filePreview);
3000
3022
  }).catch((err) => {
@@ -3082,10 +3104,24 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
3082
3104
  }
3083
3105
  function renderFilesElement(element, ctx, wrapper, pathKey) {
3084
3106
  const state = ctx.state;
3085
- 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);
3086
3123
  const resultsWrapper = document.createElement("div");
3087
3124
  resultsWrapper.className = "space-y-4";
3088
- const initialFiles = ctx.prefill[element.key] || [];
3089
3125
  if (initialFiles.length > 0) {
3090
3126
  initialFiles.forEach((resourceId) => {
3091
3127
  renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
@@ -3116,6 +3152,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
3116
3152
  };
3117
3153
  const filesWrapper = document.createElement("div");
3118
3154
  filesWrapper.className = "space-y-2";
3155
+ filesWrapper.dataset.filesWrapper = pathKey;
3119
3156
  const filesPicker = document.createElement("input");
3120
3157
  filesPicker.type = "file";
3121
3158
  filesPicker.name = pathKey;
@@ -3165,10 +3202,24 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
3165
3202
  const state = ctx.state;
3166
3203
  const minFiles = element.minCount ?? 0;
3167
3204
  const maxFiles = element.maxCount ?? Infinity;
3168
- 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);
3169
3221
  const resultsWrapper = document.createElement("div");
3170
3222
  resultsWrapper.className = "space-y-4";
3171
- const initialFiles = ctx.prefill[element.key] || [];
3172
3223
  if (initialFiles.length > 0) {
3173
3224
  initialFiles.forEach((resourceId) => {
3174
3225
  renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
@@ -3184,6 +3235,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
3184
3235
  } else {
3185
3236
  const filesWrapper = document.createElement("div");
3186
3237
  filesWrapper.className = "space-y-2";
3238
+ filesWrapper.dataset.filesWrapper = pathKey;
3187
3239
  const filesPicker = document.createElement("input");
3188
3240
  filesPicker.type = "file";
3189
3241
  filesPicker.name = pathKey;
@@ -3297,10 +3349,9 @@ function validateFileElement(element, key, context) {
3297
3349
  };
3298
3350
  if (isMultipleField) {
3299
3351
  const fullKey = pathJoin(path, key);
3300
- const pickerInput = scopeRoot.querySelector(
3301
- `input[type="file"][name="${fullKey}"]`
3352
+ const filesWrapper = scopeRoot.querySelector(
3353
+ `[data-files-wrapper="${fullKey}"]`
3302
3354
  );
3303
- const filesWrapper = pickerInput?.closest(".space-y-2");
3304
3355
  const container = filesWrapper?.querySelector(".files-list") || null;
3305
3356
  const resourceIds = [];
3306
3357
  if (container) {
@@ -3559,15 +3610,16 @@ function createEditColourUI(value, pathKey, ctx) {
3559
3610
  }
3560
3611
  function renderColourElement(element, ctx, wrapper, pathKey) {
3561
3612
  const state = ctx.state;
3613
+ const readonly = isElementReadonly(element, state, ctx);
3562
3614
  const initialValue = ctx.prefill[element.key] || element.default || "#000000";
3563
- if (state.config.readonly) {
3615
+ if (readonly) {
3564
3616
  const readonlyUI = createReadonlyColourUI(initialValue);
3565
3617
  wrapper.appendChild(readonlyUI);
3566
3618
  } else {
3567
3619
  const editUI = createEditColourUI(initialValue, pathKey, ctx);
3568
3620
  wrapper.appendChild(editUI);
3569
3621
  }
3570
- if (!state.config.readonly) {
3622
+ if (!readonly) {
3571
3623
  const colourHint = document.createElement("p");
3572
3624
  colourHint.className = "mt-1";
3573
3625
  colourHint.style.cssText = `
@@ -3580,6 +3632,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
3580
3632
  }
3581
3633
  function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3582
3634
  const state = ctx.state;
3635
+ const readonly = isElementReadonly(element, state, ctx);
3583
3636
  const prefillValues = ctx.prefill[element.key] || [];
3584
3637
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
3585
3638
  const minCount = element.minCount ?? 1;
@@ -3602,7 +3655,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3602
3655
  function addColourItem(value = "#000000", index = -1) {
3603
3656
  const itemWrapper = document.createElement("div");
3604
3657
  itemWrapper.className = "multiple-colour-item flex items-center gap-2";
3605
- if (state.config.readonly) {
3658
+ if (readonly) {
3606
3659
  const readonlyUI = createReadonlyColourUI(value);
3607
3660
  while (readonlyUI.firstChild) {
3608
3661
  itemWrapper.appendChild(readonlyUI.firstChild);
@@ -3622,7 +3675,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3622
3675
  return itemWrapper;
3623
3676
  }
3624
3677
  function updateRemoveButtons() {
3625
- if (state.config.readonly) return;
3678
+ if (readonly) return;
3626
3679
  const items = container.querySelectorAll(".multiple-colour-item");
3627
3680
  const currentCount = items.length;
3628
3681
  items.forEach((item) => {
@@ -3667,7 +3720,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3667
3720
  }
3668
3721
  let addRow = null;
3669
3722
  let countDisplay = null;
3670
- if (!state.config.readonly) {
3723
+ if (!readonly) {
3671
3724
  addRow = document.createElement("div");
3672
3725
  addRow.className = "flex items-center gap-3 mt-2";
3673
3726
  const addBtn = document.createElement("button");
@@ -3714,7 +3767,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3714
3767
  values.forEach((value) => addColourItem(value));
3715
3768
  updateAddButton();
3716
3769
  updateRemoveButtons();
3717
- if (!state.config.readonly) {
3770
+ if (!readonly) {
3718
3771
  const hint = document.createElement("p");
3719
3772
  hint.className = "mt-1";
3720
3773
  hint.style.cssText = `
@@ -3783,7 +3836,9 @@ function validateColourElement(element, key, context) {
3783
3836
  return normalized;
3784
3837
  };
3785
3838
  if (element.multiple) {
3786
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${key}["].colour-hex-input`);
3839
+ const hexInputs = scopeRoot.querySelectorAll(
3840
+ `[name^="${key}["].colour-hex-input`
3841
+ );
3787
3842
  const values = [];
3788
3843
  hexInputs.forEach((input, index) => {
3789
3844
  const val = input?.value ?? "";
@@ -3830,7 +3885,9 @@ function updateColourField(element, fieldPath, value, context) {
3830
3885
  );
3831
3886
  return;
3832
3887
  }
3833
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["].colour-hex-input`);
3888
+ const hexInputs = scopeRoot.querySelectorAll(
3889
+ `[name^="${fieldPath}["].colour-hex-input`
3890
+ );
3834
3891
  hexInputs.forEach((hexInput, index) => {
3835
3892
  if (index < value.length) {
3836
3893
  const normalized = normalizeColourValue(value[index]);
@@ -4025,6 +4082,7 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4025
4082
  );
4026
4083
  }
4027
4084
  const state = ctx.state;
4085
+ const readonly = isElementReadonly(element, state, ctx);
4028
4086
  const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
4029
4087
  const initialValue = ctx.prefill[element.key] ?? defaultValue;
4030
4088
  const sliderUI = createSliderUI(
@@ -4032,10 +4090,10 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4032
4090
  pathKey,
4033
4091
  element,
4034
4092
  ctx,
4035
- state.config.readonly
4093
+ readonly
4036
4094
  );
4037
4095
  wrapper.appendChild(sliderUI);
4038
- if (!state.config.readonly) {
4096
+ if (!readonly) {
4039
4097
  const hint = document.createElement("p");
4040
4098
  hint.className = "mt-1";
4041
4099
  hint.style.cssText = `
@@ -4059,6 +4117,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4059
4117
  );
4060
4118
  }
4061
4119
  const state = ctx.state;
4120
+ const readonly = isElementReadonly(element, state, ctx);
4062
4121
  const prefillValues = ctx.prefill[element.key] || [];
4063
4122
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
4064
4123
  const minCount = element.minCount ?? 1;
@@ -4083,13 +4142,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4083
4142
  const itemWrapper = document.createElement("div");
4084
4143
  itemWrapper.className = "multiple-slider-item flex items-start gap-2";
4085
4144
  const tempPathKey = `${pathKey}[${container.children.length}]`;
4086
- const sliderUI = createSliderUI(
4087
- value,
4088
- tempPathKey,
4089
- element,
4090
- ctx,
4091
- state.config.readonly
4092
- );
4145
+ const sliderUI = createSliderUI(value, tempPathKey, element, ctx, readonly);
4093
4146
  sliderUI.style.flex = "1";
4094
4147
  itemWrapper.appendChild(sliderUI);
4095
4148
  if (index === -1) {
@@ -4101,7 +4154,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4101
4154
  return itemWrapper;
4102
4155
  }
4103
4156
  function updateRemoveButtons() {
4104
- if (state.config.readonly) return;
4157
+ if (readonly) return;
4105
4158
  const items = container.querySelectorAll(".multiple-slider-item");
4106
4159
  const currentCount = items.length;
4107
4160
  items.forEach((item) => {
@@ -4147,7 +4200,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4147
4200
  }
4148
4201
  let addRow = null;
4149
4202
  let countDisplay = null;
4150
- if (!state.config.readonly) {
4203
+ if (!readonly) {
4151
4204
  addRow = document.createElement("div");
4152
4205
  addRow.className = "flex items-center gap-3 mt-2";
4153
4206
  const addBtn = document.createElement("button");
@@ -4193,7 +4246,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4193
4246
  values.forEach((value) => addSliderItem(value));
4194
4247
  updateAddButton();
4195
4248
  updateRemoveButtons();
4196
- if (!state.config.readonly) {
4249
+ if (!readonly) {
4197
4250
  const hint = document.createElement("p");
4198
4251
  hint.className = "mt-1";
4199
4252
  hint.style.cssText = `
@@ -4432,9 +4485,7 @@ function mergeWithDefaults(prefill, defaults) {
4432
4485
  }
4433
4486
  function extractRootFormData(formRoot) {
4434
4487
  const data = {};
4435
- const inputs = formRoot.querySelectorAll(
4436
- "input, select, textarea"
4437
- );
4488
+ const inputs = formRoot.querySelectorAll("input, select, textarea");
4438
4489
  inputs.forEach((input) => {
4439
4490
  const fieldName = input.getAttribute("name");
4440
4491
  if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
@@ -4498,7 +4549,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4498
4549
  } else {
4499
4550
  itemsWrap.className = `grid grid-cols-${columns} gap-4`;
4500
4551
  }
4501
- if (!ctx.state.config.readonly) {
4552
+ const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
4553
+ if (!containerIsReadonly) {
4502
4554
  const hintsElement = createPrefillHints(element, pathKey);
4503
4555
  if (hintsElement) {
4504
4556
  containerWrap.appendChild(hintsElement);
@@ -4513,12 +4565,15 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4513
4565
  // Merged prefill with defaults for enableIf evaluation
4514
4566
  formData: ctx.formData ?? ctx.prefill,
4515
4567
  // Complete root data for enableIf evaluation
4516
- state: ctx.state
4568
+ state: ctx.state,
4569
+ inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
4517
4570
  };
4518
4571
  element.elements.forEach((child) => {
4519
4572
  if (child.hidden || child.type === "hidden") {
4520
4573
  const prefillVal = containerPrefill[child.key] ?? child.default ?? null;
4521
- itemsWrap.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
4574
+ itemsWrap.appendChild(
4575
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
4576
+ );
4522
4577
  } else {
4523
4578
  itemsWrap.appendChild(renderElement(child, subCtx));
4524
4579
  }
@@ -4528,13 +4583,15 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4528
4583
  }
4529
4584
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4530
4585
  const state = ctx.state;
4586
+ const containerIsReadonly = isElementReadonly(element, state, ctx);
4587
+ const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
4531
4588
  const containerWrap = document.createElement("div");
4532
4589
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
4533
4590
  const countDisplay = document.createElement("span");
4534
4591
  countDisplay.className = "text-sm text-gray-500";
4535
4592
  const itemsWrap = document.createElement("div");
4536
4593
  itemsWrap.className = "space-y-4";
4537
- if (!ctx.state.config.readonly) {
4594
+ if (!containerIsReadonly) {
4538
4595
  const hintsElement = createPrefillHints(element, element.key);
4539
4596
  if (hintsElement) {
4540
4597
  containerWrap.appendChild(hintsElement);
@@ -4572,8 +4629,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4572
4629
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4573
4630
  prefill: childDefaults,
4574
4631
  // Defaults for enableIf evaluation
4575
- formData: currentFormData
4632
+ formData: currentFormData,
4576
4633
  // Current root data from DOM for enableIf
4634
+ inheritedReadonly: childInheritedReadonly
4577
4635
  };
4578
4636
  const item = document.createElement("div");
4579
4637
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4587,13 +4645,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4587
4645
  }
4588
4646
  element.elements.forEach((child) => {
4589
4647
  if (child.hidden || child.type === "hidden") {
4590
- 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
+ );
4591
4654
  } else {
4592
4655
  childWrapper.appendChild(renderElement(child, subCtx));
4593
4656
  }
4594
4657
  });
4595
4658
  item.appendChild(childWrapper);
4596
- if (!state.config.readonly) {
4659
+ if (!containerIsReadonly) {
4597
4660
  const rem = document.createElement("button");
4598
4661
  rem.type = "button";
4599
4662
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4643,8 +4706,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4643
4706
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4644
4707
  prefill: mergedPrefill,
4645
4708
  // Merged prefill with defaults for enableIf
4646
- formData: ctx.formData ?? ctx.prefill
4709
+ formData: ctx.formData ?? ctx.prefill,
4647
4710
  // Complete root data for enableIf
4711
+ inheritedReadonly: childInheritedReadonly
4648
4712
  };
4649
4713
  const item = document.createElement("div");
4650
4714
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4659,13 +4723,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4659
4723
  element.elements.forEach((child) => {
4660
4724
  if (child.hidden || child.type === "hidden") {
4661
4725
  const prefillVal = prefillObj?.[child.key] ?? child.default ?? null;
4662
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
4726
+ childWrapper.appendChild(
4727
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
4728
+ );
4663
4729
  } else {
4664
4730
  childWrapper.appendChild(renderElement(child, subCtx));
4665
4731
  }
4666
4732
  });
4667
4733
  item.appendChild(childWrapper);
4668
- if (!state.config.readonly) {
4734
+ if (!containerIsReadonly) {
4669
4735
  const rem = document.createElement("button");
4670
4736
  rem.type = "button";
4671
4737
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4688,7 +4754,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4688
4754
  itemsWrap.appendChild(item);
4689
4755
  });
4690
4756
  }
4691
- if (!state.config.readonly) {
4757
+ if (!containerIsReadonly) {
4692
4758
  while (countItems() < min) {
4693
4759
  const idx = countItems();
4694
4760
  const subCtx = {
@@ -4696,8 +4762,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4696
4762
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4697
4763
  prefill: childDefaults,
4698
4764
  // Defaults for enableIf evaluation
4699
- formData: ctx.formData ?? ctx.prefill
4765
+ formData: ctx.formData ?? ctx.prefill,
4700
4766
  // Complete root data for enableIf
4767
+ inheritedReadonly: childInheritedReadonly
4701
4768
  };
4702
4769
  const item = document.createElement("div");
4703
4770
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4711,7 +4778,12 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4711
4778
  }
4712
4779
  element.elements.forEach((child) => {
4713
4780
  if (child.hidden || child.type === "hidden") {
4714
- 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
+ );
4715
4787
  } else {
4716
4788
  childWrapper.appendChild(renderElement(child, subCtx));
4717
4789
  }
@@ -4743,7 +4815,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4743
4815
  }
4744
4816
  }
4745
4817
  containerWrap.appendChild(itemsWrap);
4746
- if (!state.config.readonly) {
4818
+ if (!containerIsReadonly) {
4747
4819
  const addRow = document.createElement("div");
4748
4820
  addRow.className = "flex items-center gap-3 mt-2";
4749
4821
  addRow.appendChild(createAddButton());
@@ -4902,8 +4974,10 @@ function updateContainerField(element, fieldPath, value, context) {
4902
4974
  const filesKey = richChild.filesKey ?? "files";
4903
4975
  const containerValue = itemValue;
4904
4976
  const compositeValue = {};
4905
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
4906
- 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];
4907
4981
  if (Object.keys(compositeValue).length > 0) {
4908
4982
  instance.updateField(childPath, compositeValue);
4909
4983
  }
@@ -4939,8 +5013,10 @@ function updateContainerField(element, fieldPath, value, context) {
4939
5013
  const filesKey = richChild.filesKey ?? "files";
4940
5014
  const containerValue = value;
4941
5015
  const compositeValue = {};
4942
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
4943
- 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];
4944
5020
  if (Object.keys(compositeValue).length > 0) {
4945
5021
  instance.updateField(childPath, compositeValue);
4946
5022
  }
@@ -5278,12 +5354,21 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5278
5354
  const hiddenInput = document.createElement("input");
5279
5355
  hiddenInput.type = "hidden";
5280
5356
  hiddenInput.name = pathKey;
5281
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
5357
+ hiddenInput.value = JSON.stringify({
5358
+ [cellsKey]: cells,
5359
+ [mergesKey]: merges
5360
+ });
5282
5361
  wrapper.appendChild(hiddenInput);
5283
5362
  function persistValue() {
5284
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
5363
+ hiddenInput.value = JSON.stringify({
5364
+ [cellsKey]: cells,
5365
+ [mergesKey]: merges
5366
+ });
5285
5367
  if (instance) {
5286
- instance.triggerOnChange(pathKey, { [cellsKey]: cells, [mergesKey]: merges });
5368
+ instance.triggerOnChange(pathKey, {
5369
+ [cellsKey]: cells,
5370
+ [mergesKey]: merges
5371
+ });
5287
5372
  }
5288
5373
  }
5289
5374
  hiddenInput._applyExternalUpdate = (data) => {
@@ -5342,9 +5427,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5342
5427
  rebuild();
5343
5428
  } catch (e) {
5344
5429
  const errMsg = e instanceof Error ? e.message : String(e);
5345
- console.error(
5346
- t("tableImportError", state).replace("{error}", errMsg)
5347
- );
5430
+ console.error(t("tableImportError", state).replace("{error}", errMsg));
5348
5431
  } finally {
5349
5432
  overlay.remove();
5350
5433
  }
@@ -5492,15 +5575,19 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5492
5575
  contextMenu.style.display = "none";
5493
5576
  }
5494
5577
  const menuDismissCtrl = new AbortController();
5495
- document.addEventListener("mousedown", (e) => {
5496
- if (!wrapper.isConnected) {
5497
- menuDismissCtrl.abort();
5498
- return;
5499
- }
5500
- if (!contextMenu.contains(e.target)) {
5501
- hideContextMenu();
5502
- }
5503
- }, { 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
+ );
5504
5591
  function applySelectionStyles() {
5505
5592
  const range = selectionRange(sel);
5506
5593
  const allTds = tableEl.querySelectorAll("td[data-row]");
@@ -5846,7 +5933,9 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5846
5933
  if (!anchor) return;
5847
5934
  const text = e.clipboardData?.getData("text/plain") ?? "";
5848
5935
  const isMultiCell = text.includes(" ") || text.split(/\r?\n/).filter((l) => l).length > 1;
5849
- const editing = tableEl.querySelector("[contenteditable='true']");
5936
+ const editing = tableEl.querySelector(
5937
+ "[contenteditable='true']"
5938
+ );
5850
5939
  if (editing && !isMultiCell) return;
5851
5940
  e.preventDefault();
5852
5941
  if (editing) {
@@ -6002,7 +6091,11 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6002
6091
  }
6003
6092
  }
6004
6093
  function updateTopZoneOverlays(mx, wr, tblR, scrollL, active) {
6005
- 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
+ ) : [];
6006
6099
  let closestColIdx = -1;
6007
6100
  let closestColDist = Infinity;
6008
6101
  let closestBorderX = -1;
@@ -6020,7 +6113,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6020
6113
  if (borderDist < closestBorderDist && borderDist < 20) {
6021
6114
  closestBorderDist = borderDist;
6022
6115
  closestBorderX = cellRect.right - wr.left + scrollL;
6023
- closestAfterCol = parseInt(headerCells[i].getAttribute("data-col") ?? "0", 10);
6116
+ closestAfterCol = parseInt(
6117
+ headerCells[i].getAttribute("data-col") ?? "0",
6118
+ 10
6119
+ );
6024
6120
  }
6025
6121
  }
6026
6122
  colRemoveBtns.forEach((btn, idx) => {
@@ -6075,7 +6171,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6075
6171
  closestRowBorderDist = borderDist;
6076
6172
  closestBorderY = trRect.bottom - wr.top;
6077
6173
  const firstTd = allRowEls[i].querySelector("td[data-row]");
6078
- closestAfterRow = parseInt(firstTd?.getAttribute("data-row") ?? "0", 10);
6174
+ closestAfterRow = parseInt(
6175
+ firstTd?.getAttribute("data-row") ?? "0",
6176
+ 10
6177
+ );
6079
6178
  }
6080
6179
  }
6081
6180
  rowRemoveBtns.forEach((btn, idx) => {
@@ -6101,7 +6200,8 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6101
6200
  let rafPending = false;
6102
6201
  tableWrapper.onmousemove = (e) => {
6103
6202
  const target = e.target;
6104
- if (target.tagName === "BUTTON" && target.parentElement === tableWrapper) return;
6203
+ if (target.tagName === "BUTTON" && target.parentElement === tableWrapper)
6204
+ return;
6105
6205
  if (rafPending) return;
6106
6206
  rafPending = true;
6107
6207
  const mx = e.clientX;
@@ -6160,6 +6260,7 @@ function isTableDataWithFieldNames(v, cellsKey) {
6160
6260
  }
6161
6261
  function renderTableElement(element, ctx, wrapper, pathKey) {
6162
6262
  const state = ctx.state;
6263
+ const readonly = isElementReadonly(element, state, ctx);
6163
6264
  const rawPrefill = ctx.prefill[element.key];
6164
6265
  const cellsKey = element.fieldNames?.cells ?? "cells";
6165
6266
  const mergesKey = element.fieldNames?.merges ?? "merges";
@@ -6194,11 +6295,13 @@ function renderTableElement(element, ctx, wrapper, pathKey) {
6194
6295
  const cols = rows > 0 ? initialData.cells[0].length : 0;
6195
6296
  const err = validateMerges(initialData.merges, rows, cols);
6196
6297
  if (err) {
6197
- console.warn(`Table "${element.key}": invalid prefill merges stripped (${err})`);
6298
+ console.warn(
6299
+ `Table "${element.key}": invalid prefill merges stripped (${err})`
6300
+ );
6198
6301
  initialData = { ...initialData, merges: [] };
6199
6302
  }
6200
6303
  }
6201
- if (state.config.readonly) {
6304
+ if (readonly) {
6202
6305
  renderReadonlyTable(initialData, wrapper);
6203
6306
  } else {
6204
6307
  renderEditTable(element, initialData, pathKey, ctx, wrapper);
@@ -6875,20 +6978,26 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
6875
6978
  });
6876
6979
  let mentionTooltip = null;
6877
6980
  backdrop.addEventListener("mouseover", (e) => {
6878
- const mark = e.target.closest?.("mark");
6981
+ const mark = e.target.closest?.(
6982
+ "mark"
6983
+ );
6879
6984
  if (!mark?.dataset.rid) return;
6880
6985
  mentionTooltip = removePortalTooltip(mentionTooltip);
6881
6986
  mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
6882
6987
  });
6883
6988
  backdrop.addEventListener("mouseout", (e) => {
6884
- const mark = e.target.closest?.("mark");
6989
+ const mark = e.target.closest?.(
6990
+ "mark"
6991
+ );
6885
6992
  if (!mark) return;
6886
6993
  const related = e.relatedTarget;
6887
6994
  if (related?.closest?.("mark")) return;
6888
6995
  mentionTooltip = removePortalTooltip(mentionTooltip);
6889
6996
  });
6890
6997
  backdrop.addEventListener("mousedown", (e) => {
6891
- const mark = e.target.closest?.("mark");
6998
+ const mark = e.target.closest?.(
6999
+ "mark"
7000
+ );
6892
7001
  if (!mark) return;
6893
7002
  mentionTooltip = removePortalTooltip(mentionTooltip);
6894
7003
  const marks = backdrop.querySelectorAll("mark");
@@ -7132,11 +7241,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7132
7241
  textarea.addEventListener("keydown", (e) => {
7133
7242
  if (!dropdownState.open) return;
7134
7243
  const labels = buildFileLabelsFromClosure();
7135
- const filtered = filterFilesForDropdown(
7136
- dropdownState.query,
7137
- files,
7138
- labels
7139
- );
7244
+ const filtered = filterFilesForDropdown(dropdownState.query, files, labels);
7140
7245
  if (e.key === "ArrowDown") {
7141
7246
  e.preventDefault();
7142
7247
  dropdownState.selectedIndex = Math.min(
@@ -7463,6 +7568,7 @@ function renderReadonlyMode(_element, ctx, wrapper, _pathKey, value) {
7463
7568
  }
7464
7569
  function renderRichInputElement(element, ctx, wrapper, pathKey) {
7465
7570
  const state = ctx.state;
7571
+ const readonly = isElementReadonly(element, state, ctx);
7466
7572
  const textKey = element.textKey ?? "text";
7467
7573
  const filesKey = element.filesKey ?? "files";
7468
7574
  let initialValue;
@@ -7500,7 +7606,7 @@ function renderRichInputElement(element, ctx, wrapper, pathKey) {
7500
7606
  });
7501
7607
  }
7502
7608
  }
7503
- if (state.config.readonly) {
7609
+ if (readonly) {
7504
7610
  renderReadonlyMode(element, ctx, wrapper, pathKey, initialValue);
7505
7611
  } else {
7506
7612
  if (!state.config.uploadFile) {
@@ -7562,9 +7668,7 @@ function validateRichInputElement(element, key, context) {
7562
7668
  }
7563
7669
  }
7564
7670
  if (element.maxFiles != null && files.length > element.maxFiles) {
7565
- errors.push(
7566
- `${key}: ${t("maxFiles", state, { max: element.maxFiles })}`
7567
- );
7671
+ errors.push(`${key}: ${t("maxFiles", state, { max: element.maxFiles })}`);
7568
7672
  }
7569
7673
  }
7570
7674
  return { value, errors, spread: !!element.flatOutput };
@@ -7680,9 +7784,7 @@ function shouldDisableElement(element, ctx) {
7680
7784
  return false;
7681
7785
  }
7682
7786
  function extractDOMValue(fieldPath, formRoot) {
7683
- const input = formRoot.querySelector(
7684
- `[name="${fieldPath}"]`
7685
- );
7787
+ const input = formRoot.querySelector(`[name="${fieldPath}"]`);
7686
7788
  if (!input) {
7687
7789
  return void 0;
7688
7790
  }
@@ -7727,9 +7829,7 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7727
7829
  `[data-container-item="${containerKey}[${containerIndex}]"]`
7728
7830
  );
7729
7831
  if (containerItemElement) {
7730
- const inputs = containerItemElement.querySelectorAll(
7731
- "input, select, textarea"
7732
- );
7832
+ const inputs = containerItemElement.querySelectorAll("input, select, textarea");
7733
7833
  inputs.forEach((input) => {
7734
7834
  const fieldName = input.getAttribute("name");
7735
7835
  if (fieldName) {
@@ -7781,7 +7881,10 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7781
7881
  wrapper.setAttribute("data-conditionally-disabled", "true");
7782
7882
  }
7783
7883
  } catch (error) {
7784
- 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
+ );
7785
7888
  }
7786
7889
  }
7787
7890
  function setupEnableIfListeners(wrapper, element, ctx) {
@@ -7802,14 +7905,10 @@ function setupEnableIfListeners(wrapper, element, ctx) {
7802
7905
  } else {
7803
7906
  dependencyFieldPath = dependencyKey;
7804
7907
  }
7805
- const dependencyInput = formRoot.querySelector(
7806
- `[name="${dependencyFieldPath}"]`
7807
- );
7908
+ const dependencyInput = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7808
7909
  if (!dependencyInput) {
7809
7910
  const observer = new MutationObserver(() => {
7810
- const input = formRoot.querySelector(
7811
- `[name="${dependencyFieldPath}"]`
7812
- );
7911
+ const input = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7813
7912
  if (input) {
7814
7913
  input.addEventListener("change", () => {
7815
7914
  reevaluateEnableIf(wrapper, element, ctx);
@@ -7956,7 +8055,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
7956
8055
  default: {
7957
8056
  const unsupported = document.createElement("div");
7958
8057
  unsupported.className = "text-red-500 text-sm";
7959
- unsupported.textContent = t("unsupportedFieldType", ctx.state, { type: element.type });
8058
+ unsupported.textContent = t("unsupportedFieldType", ctx.state, {
8059
+ type: element.type
8060
+ });
7960
8061
  wrapper.appendChild(unsupported);
7961
8062
  }
7962
8063
  }
@@ -8908,7 +9009,10 @@ var FormBuilderInstance = class {
8908
9009
  );
8909
9010
  if (componentResult !== null) {
8910
9011
  errors.push(...componentResult.errors);
8911
- return { value: componentResult.value, spread: !!componentResult.spread };
9012
+ return {
9013
+ value: componentResult.value,
9014
+ spread: !!componentResult.spread
9015
+ };
8912
9016
  }
8913
9017
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
8914
9018
  return { value: null, spread: false };
@@ -8917,10 +9021,7 @@ var FormBuilderInstance = class {
8917
9021
  this.state.schema.elements.forEach((element) => {
8918
9022
  if (element.enableIf) {
8919
9023
  try {
8920
- const shouldEnable = evaluateEnableCondition(
8921
- element.enableIf,
8922
- data
8923
- );
9024
+ const shouldEnable = evaluateEnableCondition(element.enableIf, data);
8924
9025
  if (!shouldEnable) {
8925
9026
  return;
8926
9027
  }
@@ -9215,7 +9316,10 @@ var FormBuilderInstance = class {
9215
9316
  disabledWrapper.className = "fb-field-wrapper-disabled";
9216
9317
  disabledWrapper.style.display = "none";
9217
9318
  disabledWrapper.setAttribute("data-field-key", element.key);
9218
- disabledWrapper.setAttribute("data-conditionally-disabled", "true");
9319
+ disabledWrapper.setAttribute(
9320
+ "data-conditionally-disabled",
9321
+ "true"
9322
+ );
9219
9323
  wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
9220
9324
  }
9221
9325
  } catch (error) {