@dmitryvim/form-builder 0.2.25 → 0.2.27

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.
@@ -10,7 +10,10 @@ function t(key, state, params) {
10
10
  let text = (localeTranslations == null ? void 0 : localeTranslations[key]) || (fallbackTranslations == null ? void 0 : fallbackTranslations[key]) || key;
11
11
  if (params) {
12
12
  for (const [paramKey, paramValue] of Object.entries(params)) {
13
- text = text.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
13
+ text = text.replace(
14
+ new RegExp(`\\{${paramKey}\\}`, "g"),
15
+ String(paramValue)
16
+ );
14
17
  }
15
18
  }
16
19
  return text;
@@ -255,6 +258,9 @@ function validateSchema(schema) {
255
258
  }
256
259
 
257
260
  // src/utils/helpers.ts
261
+ function isElementReadonly(element, state, ctx) {
262
+ return element.readonly === true || state.config.readonly === true || (ctx == null ? void 0 : ctx.inheritedReadonly) === true;
263
+ }
258
264
  function isPlainObject(obj) {
259
265
  return obj && typeof obj === "object" && obj.constructor === Object;
260
266
  }
@@ -414,6 +420,7 @@ function createCharCounter(element, input, isTextarea = false) {
414
420
  }
415
421
  function renderTextElement(element, ctx, wrapper, pathKey) {
416
422
  const state = ctx.state;
423
+ const readonly = isElementReadonly(element, state, ctx);
417
424
  const inputWrapper = document.createElement("div");
418
425
  inputWrapper.style.cssText = "position: relative;";
419
426
  const textInput = document.createElement("input");
@@ -424,7 +431,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
424
431
  padding-right: 60px;
425
432
  border: var(--fb-border-width) solid var(--fb-border-color);
426
433
  border-radius: var(--fb-border-radius);
427
- background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
434
+ background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
428
435
  color: var(--fb-text-color);
429
436
  font-size: var(--fb-font-size);
430
437
  font-family: var(--fb-font-family);
@@ -435,8 +442,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
435
442
  textInput.name = pathKey;
436
443
  textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
437
444
  textInput.value = ctx.prefill[element.key] || element.default || "";
438
- textInput.readOnly = state.config.readonly;
439
- if (!state.config.readonly) {
445
+ textInput.readOnly = readonly;
446
+ if (!readonly) {
440
447
  textInput.addEventListener("focus", () => {
441
448
  textInput.style.borderColor = "var(--fb-border-focus-color)";
442
449
  textInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
@@ -457,7 +464,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
457
464
  }
458
465
  });
459
466
  }
460
- if (!state.config.readonly && ctx.instance) {
467
+ if (!readonly && ctx.instance) {
461
468
  const handleChange = () => {
462
469
  const value = textInput.value === "" ? null : textInput.value;
463
470
  ctx.instance.triggerOnChange(pathKey, value);
@@ -466,7 +473,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
466
473
  textInput.addEventListener("input", handleChange);
467
474
  }
468
475
  inputWrapper.appendChild(textInput);
469
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
476
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
470
477
  const counter = createCharCounter(element, textInput, false);
471
478
  inputWrapper.appendChild(counter);
472
479
  }
@@ -475,6 +482,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
475
482
  function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
476
483
  var _a, _b;
477
484
  const state = ctx.state;
485
+ const readonly = isElementReadonly(element, state, ctx);
478
486
  const prefillValues = ctx.prefill[element.key] || [];
479
487
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
480
488
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -506,7 +514,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
506
514
  padding-right: 60px;
507
515
  border: var(--fb-border-width) solid var(--fb-border-color);
508
516
  border-radius: var(--fb-border-radius);
509
- background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
517
+ background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
510
518
  color: var(--fb-text-color);
511
519
  font-size: var(--fb-font-size);
512
520
  font-family: var(--fb-font-family);
@@ -516,8 +524,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
516
524
  `;
517
525
  textInput.placeholder = element.placeholder || t("placeholderText", state);
518
526
  textInput.value = value;
519
- textInput.readOnly = state.config.readonly;
520
- if (!state.config.readonly) {
527
+ textInput.readOnly = readonly;
528
+ if (!readonly) {
521
529
  textInput.addEventListener("focus", () => {
522
530
  textInput.style.borderColor = "var(--fb-border-focus-color)";
523
531
  textInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
@@ -538,7 +546,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
538
546
  }
539
547
  });
540
548
  }
541
- if (!state.config.readonly && ctx.instance) {
549
+ if (!readonly && ctx.instance) {
542
550
  const handleChange = () => {
543
551
  const value2 = textInput.value === "" ? null : textInput.value;
544
552
  ctx.instance.triggerOnChange(textInput.name, value2);
@@ -547,7 +555,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
547
555
  textInput.addEventListener("input", handleChange);
548
556
  }
549
557
  inputContainer.appendChild(textInput);
550
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
558
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
551
559
  const counter = createCharCounter(element, textInput, false);
552
560
  inputContainer.appendChild(counter);
553
561
  }
@@ -561,7 +569,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
561
569
  return itemWrapper;
562
570
  }
563
571
  function updateRemoveButtons() {
564
- if (state.config.readonly) return;
572
+ if (readonly) return;
565
573
  const items = container.querySelectorAll(".multiple-text-item");
566
574
  const currentCount = items.length;
567
575
  items.forEach((item) => {
@@ -606,7 +614,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
606
614
  }
607
615
  let addRow = null;
608
616
  let countDisplay = null;
609
- if (!state.config.readonly) {
617
+ if (!readonly) {
610
618
  addRow = document.createElement("div");
611
619
  addRow.className = "flex items-center gap-3 mt-2";
612
620
  const addBtn = document.createElement("button");
@@ -817,6 +825,7 @@ function applyAutoExpand(textarea) {
817
825
  }
818
826
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
819
827
  const state = ctx.state;
828
+ const readonly = isElementReadonly(element, state, ctx);
820
829
  const textareaWrapper = document.createElement("div");
821
830
  textareaWrapper.style.cssText = "position: relative;";
822
831
  const textareaInput = document.createElement("textarea");
@@ -826,8 +835,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
826
835
  textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
827
836
  textareaInput.rows = element.rows || 4;
828
837
  textareaInput.value = ctx.prefill[element.key] || element.default || "";
829
- textareaInput.readOnly = state.config.readonly;
830
- if (!state.config.readonly && ctx.instance) {
838
+ textareaInput.readOnly = readonly;
839
+ if (!readonly && ctx.instance) {
831
840
  const handleChange = () => {
832
841
  const value = textareaInput.value === "" ? null : textareaInput.value;
833
842
  ctx.instance.triggerOnChange(pathKey, value);
@@ -835,11 +844,11 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
835
844
  textareaInput.addEventListener("blur", handleChange);
836
845
  textareaInput.addEventListener("input", handleChange);
837
846
  }
838
- if (element.autoExpand || state.config.readonly) {
847
+ if (element.autoExpand || readonly) {
839
848
  applyAutoExpand(textareaInput);
840
849
  }
841
850
  textareaWrapper.appendChild(textareaInput);
842
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
851
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
843
852
  const counter = createCharCounter(element, textareaInput, true);
844
853
  textareaWrapper.appendChild(counter);
845
854
  }
@@ -848,6 +857,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
848
857
  function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
849
858
  var _a, _b;
850
859
  const state = ctx.state;
860
+ const readonly = isElementReadonly(element, state, ctx);
851
861
  const prefillValues = ctx.prefill[element.key] || [];
852
862
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
853
863
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -878,8 +888,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
878
888
  textareaInput.placeholder = element.placeholder || t("placeholderText", state);
879
889
  textareaInput.rows = element.rows || 4;
880
890
  textareaInput.value = value;
881
- textareaInput.readOnly = state.config.readonly;
882
- if (!state.config.readonly && ctx.instance) {
891
+ textareaInput.readOnly = readonly;
892
+ if (!readonly && ctx.instance) {
883
893
  const handleChange = () => {
884
894
  const value2 = textareaInput.value === "" ? null : textareaInput.value;
885
895
  ctx.instance.triggerOnChange(textareaInput.name, value2);
@@ -887,11 +897,11 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
887
897
  textareaInput.addEventListener("blur", handleChange);
888
898
  textareaInput.addEventListener("input", handleChange);
889
899
  }
890
- if (element.autoExpand || state.config.readonly) {
900
+ if (element.autoExpand || readonly) {
891
901
  applyAutoExpand(textareaInput);
892
902
  }
893
903
  textareaContainer.appendChild(textareaInput);
894
- if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
904
+ if (!readonly && (element.minLength != null || element.maxLength != null)) {
895
905
  const counter = createCharCounter(element, textareaInput, true);
896
906
  textareaContainer.appendChild(counter);
897
907
  }
@@ -905,7 +915,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
905
915
  return itemWrapper;
906
916
  }
907
917
  function updateRemoveButtons() {
908
- if (state.config.readonly) return;
918
+ if (readonly) return;
909
919
  const items = container.querySelectorAll(".multiple-textarea-item");
910
920
  const currentCount = items.length;
911
921
  items.forEach((item) => {
@@ -939,7 +949,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
939
949
  }
940
950
  let addRow = null;
941
951
  let countDisplay = null;
942
- if (!state.config.readonly) {
952
+ if (!readonly) {
943
953
  addRow = document.createElement("div");
944
954
  addRow.className = "flex items-center gap-3 mt-2";
945
955
  const addBtn = document.createElement("button");
@@ -973,7 +983,9 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
973
983
  }
974
984
  function updateAddButton() {
975
985
  if (!addRow || !countDisplay) return;
976
- const addBtn = addRow.querySelector(".add-textarea-btn");
986
+ const addBtn = addRow.querySelector(
987
+ ".add-textarea-btn"
988
+ );
977
989
  if (addBtn) {
978
990
  const disabled = values.length >= maxCount;
979
991
  addBtn.disabled = disabled;
@@ -992,7 +1004,7 @@ function validateTextareaElement(element, key, context) {
992
1004
  function updateTextareaField(element, fieldPath, value, context) {
993
1005
  updateTextField(element, fieldPath, value, context);
994
1006
  const { scopeRoot, state } = context;
995
- const shouldAutoExpand = element.autoExpand || state.config.readonly;
1007
+ const shouldAutoExpand = element.autoExpand || isElementReadonly(element, state);
996
1008
  if (!shouldAutoExpand) return;
997
1009
  if (element.multiple) {
998
1010
  const textareas = scopeRoot.querySelectorAll(
@@ -1053,6 +1065,7 @@ function createNumberRangeHint(element, input) {
1053
1065
  }
1054
1066
  function renderNumberElement(element, ctx, wrapper, pathKey) {
1055
1067
  const state = ctx.state;
1068
+ const readonly = isElementReadonly(element, state, ctx);
1056
1069
  const inputWrapper = document.createElement("div");
1057
1070
  inputWrapper.style.cssText = "position: relative;";
1058
1071
  const numberInput = document.createElement("input");
@@ -1065,8 +1078,8 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1065
1078
  if (element.max !== void 0) numberInput.max = element.max.toString();
1066
1079
  if (element.step !== void 0) numberInput.step = element.step.toString();
1067
1080
  numberInput.value = ctx.prefill[element.key] || element.default || "";
1068
- numberInput.readOnly = state.config.readonly;
1069
- if (!state.config.readonly && ctx.instance) {
1081
+ numberInput.readOnly = readonly;
1082
+ if (!readonly && ctx.instance) {
1070
1083
  const handleChange = () => {
1071
1084
  const value = numberInput.value ? parseFloat(numberInput.value) : null;
1072
1085
  ctx.instance.triggerOnChange(pathKey, value);
@@ -1075,7 +1088,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1075
1088
  numberInput.addEventListener("input", handleChange);
1076
1089
  }
1077
1090
  inputWrapper.appendChild(numberInput);
1078
- if (!state.config.readonly && (element.min != null || element.max != null)) {
1091
+ if (!readonly && (element.min != null || element.max != null)) {
1079
1092
  const counter = createNumberRangeHint(element, numberInput);
1080
1093
  inputWrapper.appendChild(counter);
1081
1094
  }
@@ -1084,6 +1097,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1084
1097
  function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1085
1098
  var _a, _b;
1086
1099
  const state = ctx.state;
1100
+ const readonly = isElementReadonly(element, state, ctx);
1087
1101
  const prefillValues = ctx.prefill[element.key] || [];
1088
1102
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
1089
1103
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -1117,8 +1131,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1117
1131
  if (element.max !== void 0) numberInput.max = element.max.toString();
1118
1132
  if (element.step !== void 0) numberInput.step = element.step.toString();
1119
1133
  numberInput.value = value.toString();
1120
- numberInput.readOnly = state.config.readonly;
1121
- if (!state.config.readonly && ctx.instance) {
1134
+ numberInput.readOnly = readonly;
1135
+ if (!readonly && ctx.instance) {
1122
1136
  const handleChange = () => {
1123
1137
  const val = numberInput.value ? parseFloat(numberInput.value) : null;
1124
1138
  ctx.instance.triggerOnChange(numberInput.name, val);
@@ -1127,7 +1141,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1127
1141
  numberInput.addEventListener("input", handleChange);
1128
1142
  }
1129
1143
  inputContainer.appendChild(numberInput);
1130
- if (!state.config.readonly && (element.min != null || element.max != null)) {
1144
+ if (!readonly && (element.min != null || element.max != null)) {
1131
1145
  const counter = createNumberRangeHint(element, numberInput);
1132
1146
  inputContainer.appendChild(counter);
1133
1147
  }
@@ -1141,7 +1155,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1141
1155
  return itemWrapper;
1142
1156
  }
1143
1157
  function updateRemoveButtons() {
1144
- if (state.config.readonly) return;
1158
+ if (readonly) return;
1145
1159
  const items = container.querySelectorAll(".multiple-number-item");
1146
1160
  const currentCount = items.length;
1147
1161
  items.forEach((item) => {
@@ -1175,7 +1189,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1175
1189
  }
1176
1190
  let addRow = null;
1177
1191
  let countDisplay = null;
1178
- if (!state.config.readonly) {
1192
+ if (!readonly) {
1179
1193
  addRow = document.createElement("div");
1180
1194
  addRow.className = "flex items-center gap-3 mt-2";
1181
1195
  const addBtn = document.createElement("button");
@@ -1383,10 +1397,11 @@ function updateNumberField(element, fieldPath, value, context) {
1383
1397
  // src/components/select.ts
1384
1398
  function renderSelectElement(element, ctx, wrapper, pathKey) {
1385
1399
  const state = ctx.state;
1400
+ const readonly = isElementReadonly(element, state, ctx);
1386
1401
  const selectInput = document.createElement("select");
1387
1402
  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";
1388
1403
  selectInput.name = pathKey;
1389
- selectInput.disabled = state.config.readonly;
1404
+ selectInput.disabled = readonly;
1390
1405
  (element.options || []).forEach((option) => {
1391
1406
  const optionEl = document.createElement("option");
1392
1407
  optionEl.value = option.value;
@@ -1396,14 +1411,14 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1396
1411
  }
1397
1412
  selectInput.appendChild(optionEl);
1398
1413
  });
1399
- if (!state.config.readonly && ctx.instance) {
1414
+ if (!readonly && ctx.instance) {
1400
1415
  const handleChange = () => {
1401
1416
  ctx.instance.triggerOnChange(pathKey, selectInput.value);
1402
1417
  };
1403
1418
  selectInput.addEventListener("change", handleChange);
1404
1419
  }
1405
1420
  wrapper.appendChild(selectInput);
1406
- if (!state.config.readonly) {
1421
+ if (!readonly) {
1407
1422
  const selectHint = document.createElement("p");
1408
1423
  selectHint.className = "text-xs text-gray-500 mt-1";
1409
1424
  selectHint.textContent = makeFieldHint(element, state);
@@ -1413,6 +1428,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1413
1428
  function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1414
1429
  var _a, _b, _c, _d;
1415
1430
  const state = ctx.state;
1431
+ const readonly = isElementReadonly(element, state, ctx);
1416
1432
  const prefillValues = ctx.prefill[element.key] || [];
1417
1433
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
1418
1434
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -1437,7 +1453,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1437
1453
  itemWrapper.className = "multiple-select-item flex items-center gap-2";
1438
1454
  const selectInput = document.createElement("select");
1439
1455
  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";
1440
- selectInput.disabled = state.config.readonly;
1456
+ selectInput.disabled = readonly;
1441
1457
  (element.options || []).forEach((option) => {
1442
1458
  const optionElement = document.createElement("option");
1443
1459
  optionElement.value = option.value;
@@ -1447,7 +1463,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1447
1463
  }
1448
1464
  selectInput.appendChild(optionElement);
1449
1465
  });
1450
- if (!state.config.readonly && ctx.instance) {
1466
+ if (!readonly && ctx.instance) {
1451
1467
  const handleChange = () => {
1452
1468
  ctx.instance.triggerOnChange(selectInput.name, selectInput.value);
1453
1469
  };
@@ -1463,7 +1479,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1463
1479
  return itemWrapper;
1464
1480
  }
1465
1481
  function updateRemoveButtons() {
1466
- if (state.config.readonly) return;
1482
+ if (readonly) return;
1467
1483
  const items = container.querySelectorAll(".multiple-select-item");
1468
1484
  const currentCount = items.length;
1469
1485
  items.forEach((item) => {
@@ -1495,7 +1511,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1495
1511
  }
1496
1512
  let addRow = null;
1497
1513
  let countDisplay = null;
1498
- if (!state.config.readonly) {
1514
+ if (!readonly) {
1499
1515
  addRow = document.createElement("div");
1500
1516
  addRow.className = "flex items-center gap-3 mt-2";
1501
1517
  const addBtn = document.createElement("button");
@@ -1543,7 +1559,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1543
1559
  values.forEach((value) => addSelectItem(value));
1544
1560
  updateAddButton();
1545
1561
  updateRemoveButtons();
1546
- if (!state.config.readonly) {
1562
+ if (!readonly) {
1547
1563
  const hint = document.createElement("p");
1548
1564
  hint.className = "text-xs text-gray-500 mt-1";
1549
1565
  hint.textContent = makeFieldHint(element, state);
@@ -1764,12 +1780,14 @@ function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onCha
1764
1780
  function renderSwitcherElement(element, ctx, wrapper, pathKey) {
1765
1781
  var _a, _b;
1766
1782
  const state = ctx.state;
1767
- const initialValue = String((_b = (_a = ctx.prefill[element.key]) != null ? _a : element.default) != null ? _b : "");
1783
+ const initialValue = String(
1784
+ (_b = (_a = ctx.prefill[element.key]) != null ? _a : element.default) != null ? _b : ""
1785
+ );
1768
1786
  const hiddenInput = document.createElement("input");
1769
1787
  hiddenInput.type = "hidden";
1770
1788
  hiddenInput.name = pathKey;
1771
1789
  hiddenInput.value = initialValue;
1772
- const readonly = state.config.readonly;
1790
+ const readonly = isElementReadonly(element, state, ctx);
1773
1791
  const onChange = !readonly && ctx.instance ? (value) => {
1774
1792
  ctx.instance.triggerOnChange(pathKey, value);
1775
1793
  } : null;
@@ -1799,7 +1817,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
1799
1817
  while (values.length < minCount) {
1800
1818
  values.push(element.default || ((_d = (_c = element.options) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || "");
1801
1819
  }
1802
- const readonly = state.config.readonly;
1820
+ const readonly = isElementReadonly(element, state, ctx);
1803
1821
  const container = document.createElement("div");
1804
1822
  container.className = "space-y-2";
1805
1823
  wrapper.appendChild(container);
@@ -2101,7 +2119,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
2101
2119
  }
2102
2120
  }
2103
2121
 
2104
- // src/components/file.ts
2122
+ // src/components/file/constraints.ts
2105
2123
  function getAllowedExtensions(accept) {
2106
2124
  if (!accept) return [];
2107
2125
  if (typeof accept === "object" && Array.isArray(accept.extensions)) {
@@ -2113,18 +2131,669 @@ function getAllowedExtensions(accept) {
2113
2131
  return [];
2114
2132
  }
2115
2133
  function isFileExtensionAllowed(fileName, allowedExtensions) {
2116
- var _a;
2134
+ var _a, _b;
2117
2135
  if (allowedExtensions.length === 0) return true;
2118
- const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
2136
+ const ext = (_b = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
2119
2137
  return allowedExtensions.includes(ext);
2120
2138
  }
2121
2139
  function isFileSizeAllowed(file, maxSizeMB) {
2122
2140
  if (maxSizeMB === Infinity) return true;
2123
2141
  return file.size <= maxSizeMB * 1024 * 1024;
2124
2142
  }
2143
+ function addPrefillFilesToIndex(initialFiles, resourceIndex) {
2144
+ var _a;
2145
+ for (const resourceId of initialFiles) {
2146
+ if (resourceIndex.has(resourceId)) continue;
2147
+ const filename = resourceId.split("/").pop() || "file";
2148
+ const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
2149
+ let fileType = "application/octet-stream";
2150
+ if (extension) {
2151
+ if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
2152
+ fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
2153
+ } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
2154
+ fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
2155
+ }
2156
+ }
2157
+ resourceIndex.set(resourceId, {
2158
+ name: filename,
2159
+ type: fileType,
2160
+ size: 0,
2161
+ uploadedAt: /* @__PURE__ */ new Date(),
2162
+ file: void 0
2163
+ });
2164
+ }
2165
+ }
2166
+
2167
+ // src/components/file/styles.ts
2168
+ var STYLE_ID = "fb-file-styles";
2169
+ function ensureFileStyles() {
2170
+ if (typeof document === "undefined") return;
2171
+ if (document.getElementById(STYLE_ID)) return;
2172
+ const style = document.createElement("style");
2173
+ style.id = STYLE_ID;
2174
+ style.setAttribute("data-fb-file-styles", "true");
2175
+ style.textContent = `
2176
+ @keyframes fb-spin { to { transform: rotate(360deg); } }
2177
+
2178
+ /* Spinner used during single-file and multi-file upload */
2179
+ .fb-spinner {
2180
+ width: 36px;
2181
+ height: 36px;
2182
+ border: 3px solid rgba(0,0,0,0.12);
2183
+ border-top-color: var(--fb-text-secondary-color, #6b7280);
2184
+ border-radius: 50%;
2185
+ animation: fb-spin 0.7s linear infinite;
2186
+ flex-shrink: 0;
2187
+ }
2188
+
2189
+ /* Base tile: fixed 160\xD7160 square, theme-aware background */
2190
+ .fb-tile {
2191
+ width: var(--fb-tile-size, 160px);
2192
+ height: var(--fb-tile-size, 160px);
2193
+ flex-shrink: 0;
2194
+ position: relative;
2195
+ overflow: hidden;
2196
+ border-radius: var(--fb-border-radius, 0.5rem);
2197
+ background: var(--fb-file-upload-bg-color, #f3f4f6);
2198
+ }
2199
+
2200
+ /* Uploaded resource tile \u2014 adds a visible border */
2201
+ .fb-tile-resource {
2202
+ border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2203
+ }
2204
+
2205
+ /* Uploading placeholder tile \u2014 dashed border, uploading indicator */
2206
+ .fb-tile-uploading {
2207
+ border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2208
+ }
2209
+
2210
+ /* "+" add-more tile */
2211
+ .fb-tile-add {
2212
+ border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2213
+ display: flex;
2214
+ align-items: center;
2215
+ justify-content: center;
2216
+ cursor: pointer;
2217
+ font-size: 32px;
2218
+ color: var(--fb-file-upload-text-color, #9ca3af);
2219
+ transition:
2220
+ border-color var(--fb-transition-duration, 200ms),
2221
+ color var(--fb-transition-duration, 200ms);
2222
+ }
2223
+ .fb-tile-add:hover {
2224
+ border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2225
+ color: var(--fb-text-color, #1f2937);
2226
+ }
2227
+
2228
+ /* Count chip shown when at maxCount */
2229
+ .fb-tile-counter {
2230
+ font-size: 11px;
2231
+ color: var(--fb-text-secondary-color, #6b7280);
2232
+ background: var(--fb-file-upload-bg-color, #f3f4f6);
2233
+ border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2234
+ border-radius: 4px;
2235
+ padding: 2px 6px;
2236
+ align-self: flex-end;
2237
+ margin-bottom: 4px;
2238
+ }
2239
+
2240
+ /* Empty-state dropzone */
2241
+ .fb-file-dropzone {
2242
+ width: 100%;
2243
+ height: 128px;
2244
+ border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2245
+ border-radius: var(--fb-border-radius, 0.5rem);
2246
+ display: flex;
2247
+ flex-direction: column;
2248
+ align-items: center;
2249
+ justify-content: center;
2250
+ gap: 4px;
2251
+ cursor: pointer;
2252
+ transition:
2253
+ border-color var(--fb-transition-duration, 200ms),
2254
+ background var(--fb-transition-duration, 200ms);
2255
+ }
2256
+ .fb-file-dropzone:hover {
2257
+ border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2258
+ background: var(--fb-background-hover-color, #f9fafb);
2259
+ }
2260
+
2261
+ /* Inline text inside tiles */
2262
+ .fb-tile-label {
2263
+ font-size: 9px;
2264
+ color: var(--fb-text-secondary-color, #6b7280);
2265
+ text-align: center;
2266
+ overflow: hidden;
2267
+ word-break: break-all;
2268
+ max-height: 28px;
2269
+ }
2270
+ .fb-tile-uploading-text {
2271
+ font-size: 8px;
2272
+ color: var(--fb-file-upload-text-color, #9ca3af);
2273
+ }
2274
+ .fb-tile-hint {
2275
+ font-size: 11px;
2276
+ color: var(--fb-file-upload-text-color, #9ca3af);
2277
+ margin-top: 4px;
2278
+ }
2279
+ .fb-tile-empty-text {
2280
+ font-size: 12px;
2281
+ color: var(--fb-text-secondary-color, #6b7280);
2282
+ padding: 4px 0;
2283
+ }
2284
+ .fb-dropzone-primary-text {
2285
+ font-size: 13px;
2286
+ color: var(--fb-text-secondary-color, #6b7280);
2287
+ }
2288
+ .fb-dropzone-hint-text {
2289
+ font-size: 11px;
2290
+ color: var(--fb-file-upload-text-color, #9ca3af);
2291
+ }
2292
+
2293
+ /* Hover overlay + X-button on resource tiles */
2294
+ .fb-tile-overlay {
2295
+ position: absolute;
2296
+ inset: 0;
2297
+ background: transparent;
2298
+ transition: background var(--fb-transition-duration, 200ms);
2299
+ display: flex;
2300
+ align-items: flex-start;
2301
+ justify-content: flex-end;
2302
+ }
2303
+ .fb-tile-resource:hover .fb-tile-overlay {
2304
+ background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.4));
2305
+ }
2306
+ .fb-tile-x-btn {
2307
+ margin: 3px;
2308
+ width: 18px;
2309
+ height: 18px;
2310
+ background: var(--fb-error-color, #ef4444);
2311
+ color: var(--fb-file-bg-color, #fff);
2312
+ border: none;
2313
+ border-radius: 50%;
2314
+ font-size: 11px;
2315
+ line-height: 1;
2316
+ cursor: pointer;
2317
+ display: flex;
2318
+ align-items: center;
2319
+ justify-content: center;
2320
+ opacity: 0;
2321
+ transition: opacity var(--fb-transition-duration, 200ms);
2322
+ }
2323
+ .fb-tile-resource:hover .fb-tile-x-btn {
2324
+ opacity: 1;
2325
+ }
2326
+
2327
+ /* Video play button overlay (readonly tiles with video thumbnails) */
2328
+ .fb-video-overlay {
2329
+ position: absolute;
2330
+ inset: 0;
2331
+ display: flex;
2332
+ align-items: center;
2333
+ justify-content: center;
2334
+ background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.25));
2335
+ }
2336
+ .fb-play-btn {
2337
+ background: var(--fb-file-bg-color, rgba(255,255,255,0.9));
2338
+ border-radius: 50%;
2339
+ display: flex;
2340
+ align-items: center;
2341
+ justify-content: center;
2342
+ }
2343
+
2344
+ /* Edit-mode local video preview wrapper */
2345
+ .fb-video-preview-wrap {
2346
+ position: relative;
2347
+ width: 100%;
2348
+ height: 100%;
2349
+ }
2350
+
2351
+ /* Hover overlay for edit-mode local video (Remove / Change buttons) */
2352
+ .fb-video-btn-overlay {
2353
+ position: absolute;
2354
+ top: 8px;
2355
+ right: 8px;
2356
+ z-index: 10;
2357
+ display: flex;
2358
+ gap: 4px;
2359
+ opacity: 0;
2360
+ transition: opacity var(--fb-transition-duration, 200ms);
2361
+ pointer-events: none;
2362
+ }
2363
+ .fb-video-preview-wrap:hover .fb-video-btn-overlay {
2364
+ opacity: 1;
2365
+ pointer-events: auto;
2366
+ }
2367
+ .fb-video-btn {
2368
+ border: none;
2369
+ border-radius: var(--fb-border-radius, 4px);
2370
+ font-size: 11px;
2371
+ padding: 4px 8px;
2372
+ cursor: pointer;
2373
+ color: #fff;
2374
+ line-height: 1.2;
2375
+ }
2376
+ .fb-video-btn-delete {
2377
+ background: rgba(220, 38, 38, 0.85);
2378
+ }
2379
+ .fb-video-btn-delete:hover {
2380
+ background: rgba(185, 28, 28, 0.95);
2381
+ }
2382
+ .fb-video-btn-change {
2383
+ background: rgba(31, 41, 55, 0.85);
2384
+ }
2385
+ .fb-video-btn-change:hover {
2386
+ background: rgba(17, 24, 39, 0.95);
2387
+ }
2388
+
2389
+ /* Tile action icon buttons (download / open / remove) \u2014 shown on tile hover */
2390
+ .fb-tile-actions {
2391
+ position: absolute;
2392
+ top: 3px;
2393
+ right: 3px;
2394
+ display: flex;
2395
+ flex-direction: row;
2396
+ gap: 3px;
2397
+ opacity: 0;
2398
+ transition: opacity var(--fb-transition-duration, 200ms);
2399
+ z-index: 10;
2400
+ }
2401
+ .fb-tile-resource:hover .fb-tile-actions {
2402
+ opacity: 1;
2403
+ }
2404
+ .fb-tile-action-btn {
2405
+ width: 28px;
2406
+ height: 28px;
2407
+ display: flex;
2408
+ align-items: center;
2409
+ justify-content: center;
2410
+ border: none;
2411
+ border-radius: 50%;
2412
+ cursor: pointer;
2413
+ background: rgba(31, 41, 55, 0.75);
2414
+ color: #fff;
2415
+ padding: 0;
2416
+ flex-shrink: 0;
2417
+ transition:
2418
+ background var(--fb-transition-duration, 200ms),
2419
+ opacity var(--fb-transition-duration, 200ms);
2420
+ }
2421
+ .fb-tile-action-btn:hover {
2422
+ background: rgba(17, 24, 39, 0.95);
2423
+ }
2424
+ .fb-tile-action-remove {
2425
+ background: rgba(220, 38, 38, 0.8);
2426
+ }
2427
+ .fb-tile-action-remove:hover {
2428
+ background: rgba(185, 28, 28, 0.95);
2429
+ }
2430
+
2431
+ /* Actions row inside zoom popup \u2014 always visible while popup is shown */
2432
+ .fb-tile-zoom-preview .fb-tile-actions {
2433
+ position: absolute;
2434
+ top: 6px;
2435
+ right: 6px;
2436
+ opacity: 1;
2437
+ z-index: 10000;
2438
+ }
2439
+
2440
+ /* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
2441
+ .fb-tile-zoom-preview {
2442
+ position: fixed;
2443
+ z-index: 9999;
2444
+ background: var(--fb-background-color, #fff);
2445
+ border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2446
+ border-radius: var(--fb-border-radius, 0.5rem);
2447
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
2448
+ padding: 4px;
2449
+ width: 350px;
2450
+ height: 350px;
2451
+ pointer-events: none;
2452
+ opacity: 0;
2453
+ transition: opacity 150ms ease;
2454
+ }
2455
+ .fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
2456
+ opacity: 1;
2457
+ }
2458
+ .fb-tile-zoom-preview-img {
2459
+ width: 100%;
2460
+ height: 100%;
2461
+ object-fit: contain;
2462
+ display: block;
2463
+ background: var(--fb-file-upload-bg-color, #f3f4f6);
2464
+ border-radius: calc(var(--fb-border-radius, 0.5rem) - 2px);
2465
+ }
2466
+ `;
2467
+ document.head.appendChild(style);
2468
+ }
2469
+
2470
+ // src/components/file/dom.ts
2471
+ var TILE_SIZE = "160px";
2472
+ function createFileTile() {
2473
+ ensureFileStyles();
2474
+ const tile = document.createElement("div");
2475
+ tile.className = "fb-tile";
2476
+ return tile;
2477
+ }
2478
+ function showFileError(container, message) {
2479
+ var _a, _b;
2480
+ const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2481
+ if (existing) existing.remove();
2482
+ const errorEl = document.createElement("div");
2483
+ errorEl.className = "file-error-message error-message";
2484
+ errorEl.style.cssText = `
2485
+ color: var(--fb-error-color);
2486
+ font-size: var(--fb-font-size-small);
2487
+ margin-top: 0.25rem;
2488
+ `;
2489
+ errorEl.textContent = message;
2490
+ (_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
2491
+ }
2492
+ function clearFileError(container) {
2493
+ var _a;
2494
+ const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2495
+ if (existing) existing.remove();
2496
+ }
2497
+ function addDeleteButton(container, state, onDelete) {
2498
+ const existingOverlay = container.querySelector(".delete-overlay");
2499
+ if (existingOverlay) existingOverlay.remove();
2500
+ const overlay = document.createElement("div");
2501
+ overlay.className = "delete-overlay absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center";
2502
+ const deleteBtn = document.createElement("button");
2503
+ deleteBtn.className = "bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors";
2504
+ deleteBtn.textContent = t("removeElement", state);
2505
+ deleteBtn.onclick = (e) => {
2506
+ e.stopPropagation();
2507
+ onDelete();
2508
+ };
2509
+ overlay.appendChild(deleteBtn);
2510
+ container.appendChild(overlay);
2511
+ }
2512
+ function findFilePicker(container) {
2513
+ var _a;
2514
+ let el = container.parentElement;
2515
+ while (el && !el.dataset.filesWrapper) {
2516
+ el = el.parentElement;
2517
+ }
2518
+ return (_a = el == null ? void 0 : el.querySelector('input[type="file"]')) != null ? _a : null;
2519
+ }
2520
+ function createUploadingTile(fileName, state) {
2521
+ ensureFileStyles();
2522
+ const tile = createFileTile();
2523
+ tile.classList.add("fb-tile-uploading");
2524
+ tile.className += " fb-uploading-tile";
2525
+ const label = fileName.length > 10 ? fileName.substring(0, 8) + "\u2026" : fileName;
2526
+ tile.innerHTML = `
2527
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:4px;">
2528
+ <div class="fb-spinner"></div>
2529
+ <div class="fb-tile-label">${escapeHtml(label)}</div>
2530
+ <div class="fb-tile-uploading-text">${escapeHtml(t("uploadingFile", state))}</div>
2531
+ </div>`;
2532
+ return tile;
2533
+ }
2534
+ function ensureTilesWrap(list) {
2535
+ const existing = list.querySelector(".fb-tiles-wrap");
2536
+ if (existing) return existing;
2537
+ const dropzone = list.querySelector(".fb-file-dropzone");
2538
+ if (dropzone) dropzone.remove();
2539
+ const tilesWrap = document.createElement("div");
2540
+ tilesWrap.className = "fb-tiles-wrap";
2541
+ tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
2542
+ const addTile = document.createElement("div");
2543
+ addTile.className = "fb-tile fb-tile-add";
2544
+ addTile.innerHTML = "+";
2545
+ tilesWrap.appendChild(addTile);
2546
+ list.appendChild(tilesWrap);
2547
+ return tilesWrap;
2548
+ }
2549
+ function setEmptyFileContainer(fileContainer, state, hint) {
2550
+ const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
2551
+ fileContainer.innerHTML = `
2552
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
2553
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
2554
+ <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
2555
+ </svg>
2556
+ <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
2557
+ ${hintHtml}
2558
+ </div>
2559
+ `;
2560
+ }
2561
+ function setupDragAndDrop(element, dropHandler) {
2562
+ element.addEventListener("dragover", (e) => {
2563
+ e.preventDefault();
2564
+ element.classList.add("border-blue-500", "bg-blue-50");
2565
+ });
2566
+ element.addEventListener("dragleave", (e) => {
2567
+ e.preventDefault();
2568
+ element.classList.remove("border-blue-500", "bg-blue-50");
2569
+ });
2570
+ element.addEventListener("drop", (e) => {
2571
+ var _a;
2572
+ e.preventDefault();
2573
+ element.classList.remove("border-blue-500", "bg-blue-50");
2574
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
2575
+ dropHandler(e.dataTransfer.files);
2576
+ }
2577
+ });
2578
+ }
2579
+
2580
+ // src/components/file/preview.ts
2581
+ var ICON_DOWNLOAD = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
2582
+ var ICON_OPEN = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
2583
+ var ICON_REMOVE = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>`;
2584
+ function canDownload(state, meta) {
2585
+ return Boolean(
2586
+ state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
2587
+ );
2588
+ }
2589
+ function canOpenInTab(state, meta) {
2590
+ return Boolean(
2591
+ state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
2592
+ );
2593
+ }
2594
+ function createTileActions(options) {
2595
+ const { canRemove, removeHandler, state, resourceId, fileName, meta } = options;
2596
+ const group = document.createElement("div");
2597
+ group.className = "fb-tile-actions";
2598
+ const makeBtn = (icon, label, cls) => {
2599
+ const btn = document.createElement("button");
2600
+ btn.type = "button";
2601
+ btn.className = `fb-tile-action-btn ${cls}`;
2602
+ btn.innerHTML = icon;
2603
+ btn.title = label;
2604
+ btn.setAttribute("aria-label", label);
2605
+ btn.addEventListener("click", (e) => {
2606
+ e.stopPropagation();
2607
+ });
2608
+ return btn;
2609
+ };
2610
+ if (canDownload(state, meta)) {
2611
+ const dlBtn = makeBtn(ICON_DOWNLOAD, t("downloadFile", state), "fb-tile-action-download");
2612
+ dlBtn.addEventListener("click", () => {
2613
+ triggerTileDownload(resourceId, fileName, state, meta);
2614
+ });
2615
+ group.appendChild(dlBtn);
2616
+ }
2617
+ if (canOpenInTab(state, meta)) {
2618
+ const openBtn = makeBtn(ICON_OPEN, t("openInNewTab", state), "fb-tile-action-open");
2619
+ openBtn.addEventListener("click", () => {
2620
+ triggerTileOpen(resourceId, state, meta).catch((err) => {
2621
+ console.error("Open failed:", err);
2622
+ });
2623
+ });
2624
+ group.appendChild(openBtn);
2625
+ }
2626
+ if (canRemove && removeHandler) {
2627
+ const rmBtn = makeBtn(ICON_REMOVE, t("removeElement", state), "fb-tile-action-remove");
2628
+ rmBtn.addEventListener("click", () => {
2629
+ removeHandler();
2630
+ });
2631
+ group.appendChild(rmBtn);
2632
+ }
2633
+ return group;
2634
+ }
2635
+ var localFileUrlCache = /* @__PURE__ */ new WeakMap();
2636
+ function getLocalFileUrl(file) {
2637
+ let url = localFileUrlCache.get(file);
2638
+ if (!url) {
2639
+ url = URL.createObjectURL(file);
2640
+ localFileUrlCache.set(file, url);
2641
+ }
2642
+ return url;
2643
+ }
2644
+ function releaseLocalFileUrl(file) {
2645
+ if (!file) return;
2646
+ const url = localFileUrlCache.get(file);
2647
+ if (url) {
2648
+ URL.revokeObjectURL(url);
2649
+ localFileUrlCache.delete(file);
2650
+ }
2651
+ }
2652
+ function triggerTileDownload(resourceId, fileName, state, meta) {
2653
+ if (state.config.downloadFile) {
2654
+ state.config.downloadFile(resourceId, fileName);
2655
+ return;
2656
+ }
2657
+ if ((meta == null ? void 0 : meta.file) instanceof File) {
2658
+ downloadBlob(meta.file, fileName || meta.file.name);
2659
+ return;
2660
+ }
2661
+ forceDownload(resourceId, fileName, state).catch((err) => {
2662
+ console.error("Download failed:", err);
2663
+ });
2664
+ }
2665
+ async function triggerTileOpen(resourceId, state, meta) {
2666
+ let url = null;
2667
+ if (state.config.getDownloadUrl) {
2668
+ url = state.config.getDownloadUrl(resourceId);
2669
+ } else if (state.config.getThumbnail) {
2670
+ url = await state.config.getThumbnail(resourceId);
2671
+ } else if ((meta == null ? void 0 : meta.file) instanceof File) {
2672
+ url = getLocalFileUrl(meta.file);
2673
+ }
2674
+ if (url) {
2675
+ window.open(url, "_blank");
2676
+ }
2677
+ }
2678
+ var sharedZoomPopup = null;
2679
+ var zoomTimer = null;
2680
+ var zoomHideTimer = null;
2681
+ var zoomOwner = null;
2682
+ function getOrCreateZoomPopup() {
2683
+ if (!sharedZoomPopup) {
2684
+ sharedZoomPopup = document.createElement("div");
2685
+ sharedZoomPopup.className = "fb-tile-zoom-preview";
2686
+ const img = document.createElement("img");
2687
+ img.className = "fb-tile-zoom-preview-img";
2688
+ sharedZoomPopup.appendChild(img);
2689
+ sharedZoomPopup.addEventListener("mouseenter", cancelHideZoomPopup);
2690
+ sharedZoomPopup.addEventListener("mouseleave", scheduleHideZoomPopup);
2691
+ }
2692
+ return sharedZoomPopup;
2693
+ }
2694
+ function positionZoomPopup(popup, tile) {
2695
+ const tileRect = tile.getBoundingClientRect();
2696
+ const popupSize = 350;
2697
+ const margin = 6;
2698
+ const padding = 8;
2699
+ let top;
2700
+ if (tileRect.top - popupSize - margin >= padding) {
2701
+ top = tileRect.top - popupSize - margin;
2702
+ } else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
2703
+ top = tileRect.bottom + margin;
2704
+ } else {
2705
+ top = Math.max(padding, Math.min(window.innerHeight - popupSize - padding, tileRect.top));
2706
+ }
2707
+ const tileCenterX = tileRect.left + tileRect.width / 2;
2708
+ let left = tileCenterX - popupSize / 2;
2709
+ left = Math.max(padding, Math.min(window.innerWidth - popupSize - padding, left));
2710
+ popup.style.top = `${top}px`;
2711
+ popup.style.left = `${left}px`;
2712
+ }
2713
+ function scheduleHideZoomPopup() {
2714
+ if (zoomHideTimer !== null) {
2715
+ clearTimeout(zoomHideTimer);
2716
+ }
2717
+ zoomHideTimer = setTimeout(() => {
2718
+ zoomHideTimer = null;
2719
+ removeZoomPopupNow();
2720
+ }, 100);
2721
+ }
2722
+ function cancelHideZoomPopup() {
2723
+ if (zoomHideTimer !== null) {
2724
+ clearTimeout(zoomHideTimer);
2725
+ zoomHideTimer = null;
2726
+ }
2727
+ }
2728
+ function removeZoomPopupNow() {
2729
+ if (zoomTimer !== null) {
2730
+ clearTimeout(zoomTimer);
2731
+ zoomTimer = null;
2732
+ }
2733
+ if (sharedZoomPopup && sharedZoomPopup.parentNode) {
2734
+ sharedZoomPopup.classList.remove("fb-tile-zoom-preview--visible");
2735
+ sharedZoomPopup.parentNode.removeChild(sharedZoomPopup);
2736
+ }
2737
+ zoomOwner = null;
2738
+ }
2739
+ function attachZoomHover(tile, src, alt, actionsEl) {
2740
+ tile.dataset.zoomSrc = src;
2741
+ tile.dataset.zoomAlt = alt;
2742
+ tile.addEventListener("mouseenter", () => {
2743
+ cancelHideZoomPopup();
2744
+ if (zoomOwner !== tile) {
2745
+ removeZoomPopupNow();
2746
+ }
2747
+ zoomOwner = tile;
2748
+ zoomTimer = setTimeout(() => {
2749
+ zoomTimer = null;
2750
+ const popup = getOrCreateZoomPopup();
2751
+ const existingActions = popup.querySelector(".fb-tile-actions");
2752
+ if (existingActions) existingActions.remove();
2753
+ const img = popup.querySelector(".fb-tile-zoom-preview-img");
2754
+ img.src = src;
2755
+ img.alt = alt;
2756
+ if (actionsEl) {
2757
+ popup.appendChild(actionsEl.cloneNode(true));
2758
+ attachClonedActionListeners(
2759
+ popup.querySelector(".fb-tile-actions"),
2760
+ actionsEl
2761
+ );
2762
+ }
2763
+ popup.style.pointerEvents = "auto";
2764
+ positionZoomPopup(popup, tile);
2765
+ document.body.appendChild(popup);
2766
+ popup.getBoundingClientRect();
2767
+ popup.classList.add("fb-tile-zoom-preview--visible");
2768
+ }, 200);
2769
+ });
2770
+ tile.addEventListener("mouseleave", () => {
2771
+ if (zoomTimer !== null) {
2772
+ clearTimeout(zoomTimer);
2773
+ zoomTimer = null;
2774
+ zoomOwner = null;
2775
+ } else {
2776
+ scheduleHideZoomPopup();
2777
+ }
2778
+ });
2779
+ }
2780
+ function attachClonedActionListeners(cloned, original) {
2781
+ const originalBtns = Array.from(original.querySelectorAll(".fb-tile-action-btn"));
2782
+ const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
2783
+ clonedBtns.forEach((clonedBtn, i) => {
2784
+ const origBtn = originalBtns[i];
2785
+ if (origBtn) {
2786
+ clonedBtn.addEventListener("click", (e) => {
2787
+ e.stopPropagation();
2788
+ origBtn.click();
2789
+ });
2790
+ }
2791
+ });
2792
+ }
2125
2793
  function renderLocalImagePreview(container, file, fileName, state) {
2126
2794
  const img = document.createElement("img");
2127
2795
  img.className = "w-full h-full object-contain";
2796
+ img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
2128
2797
  img.alt = fileName || t("previewAlt", state);
2129
2798
  const reader = new FileReader();
2130
2799
  reader.onload = (e) => {
@@ -2134,23 +2803,27 @@ function renderLocalImagePreview(container, file, fileName, state) {
2134
2803
  reader.readAsDataURL(file);
2135
2804
  container.appendChild(img);
2136
2805
  }
2137
- function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
2138
- const videoUrl = URL.createObjectURL(file);
2806
+ function setupDragDropless(container, _deps) {
2139
2807
  container.onclick = null;
2140
2808
  const newContainer = container.cloneNode(false);
2141
2809
  if (container.parentNode) {
2142
2810
  container.parentNode.replaceChild(newContainer, container);
2143
2811
  }
2812
+ return newContainer;
2813
+ }
2814
+ function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
2815
+ const videoUrl = URL.createObjectURL(file);
2816
+ const newContainer = setupDragDropless(container);
2144
2817
  newContainer.innerHTML = `
2145
- <div class="relative group h-full">
2818
+ <div class="fb-video-preview-wrap">
2146
2819
  <video class="w-full h-full object-contain" controls preload="auto" muted src="${videoUrl}">
2147
2820
  ${escapeHtml(t("videoNotSupported", state))}
2148
2821
  </video>
2149
- <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
2150
- <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
2822
+ <div class="fb-video-btn-overlay">
2823
+ <button class="fb-video-btn fb-video-btn-delete delete-file-btn">
2151
2824
  ${escapeHtml(t("removeElement", state))}
2152
2825
  </button>
2153
- <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
2826
+ <button class="fb-video-btn fb-video-btn-change change-file-btn">
2154
2827
  ${escapeHtml(t("changeButton", state))}
2155
2828
  </button>
2156
2829
  </div>
@@ -2160,20 +2833,15 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
2160
2833
  return newContainer;
2161
2834
  }
2162
2835
  function attachVideoButtonHandlers(container, resourceId, state, deps) {
2163
- const changeBtn = container.querySelector(
2164
- ".change-file-btn"
2165
- );
2836
+ const changeBtn = container.querySelector(".change-file-btn");
2166
2837
  if (changeBtn) {
2167
2838
  changeBtn.onclick = (e) => {
2839
+ var _a;
2168
2840
  e.stopPropagation();
2169
- if (deps == null ? void 0 : deps.picker) {
2170
- deps.picker.click();
2171
- }
2841
+ (_a = deps == null ? void 0 : deps.picker) == null ? void 0 : _a.click();
2172
2842
  };
2173
2843
  }
2174
- const deleteBtn = container.querySelector(
2175
- ".delete-file-btn"
2176
- );
2844
+ const deleteBtn = container.querySelector(".delete-file-btn");
2177
2845
  if (deleteBtn) {
2178
2846
  deleteBtn.onclick = (e) => {
2179
2847
  e.stopPropagation();
@@ -2193,9 +2861,6 @@ function handleVideoDelete(container, resourceId, state, deps) {
2193
2861
  if (deps == null ? void 0 : deps.fileUploadHandler) {
2194
2862
  container.onclick = deps.fileUploadHandler;
2195
2863
  }
2196
- if (deps == null ? void 0 : deps.dragHandler) {
2197
- setupDragAndDrop(container, deps.dragHandler);
2198
- }
2199
2864
  container.innerHTML = `
2200
2865
  <div class="flex flex-col items-center justify-center h-full text-gray-400">
2201
2866
  <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
@@ -2204,18 +2869,9 @@ function handleVideoDelete(container, resourceId, state, deps) {
2204
2869
  <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
2205
2870
  </div>
2206
2871
  `;
2207
- }
2208
- function renderUploadedVideoPreview(container, thumbnailUrl, _videoType, state) {
2209
- const video = document.createElement("video");
2210
- video.className = "w-full h-full object-contain";
2211
- video.controls = true;
2212
- video.preload = "metadata";
2213
- video.muted = true;
2214
- video.src = thumbnailUrl;
2215
- video.appendChild(
2216
- document.createTextNode(t("videoNotSupported", state))
2217
- );
2218
- container.appendChild(video);
2872
+ if (deps == null ? void 0 : deps.setupDrop) {
2873
+ deps.setupDrop(container);
2874
+ }
2219
2875
  }
2220
2876
  function renderDeleteButton(container, resourceId, state) {
2221
2877
  addDeleteButton(container, state, () => {
@@ -2238,13 +2894,12 @@ function renderDeleteButton(container, resourceId, state) {
2238
2894
  });
2239
2895
  }
2240
2896
  async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
2241
- if (!meta.file || !(meta.file instanceof File)) {
2242
- return;
2243
- }
2244
- if (meta.type && meta.type.startsWith("image/")) {
2897
+ var _a, _b, _c;
2898
+ if (!meta.file || !(meta.file instanceof File)) return;
2899
+ if ((_a = meta.type) == null ? void 0 : _a.startsWith("image/")) {
2245
2900
  renderLocalImagePreview(container, meta.file, fileName, state);
2246
- } else if (meta.type && meta.type.startsWith("video/")) {
2247
- const newContainer = renderLocalVideoPreview(
2901
+ } else if ((_b = meta.type) == null ? void 0 : _b.startsWith("video/")) {
2902
+ container = renderLocalVideoPreview(
2248
2903
  container,
2249
2904
  meta.file,
2250
2905
  meta.type,
@@ -2252,15 +2907,25 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
2252
2907
  state,
2253
2908
  deps
2254
2909
  );
2255
- container = newContainer;
2256
2910
  } else {
2257
- container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${escapeHtml(fileName)}</div></div>`;
2911
+ container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div style="font-size:36px;" class="mb-2">\u{1F4C1}</div><div class="text-sm">${escapeHtml(fileName)}</div></div>`;
2258
2912
  }
2259
- if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
2913
+ if (!isReadonly && !((_c = meta.type) == null ? void 0 : _c.startsWith("video/"))) {
2260
2914
  renderDeleteButton(container, resourceId, state);
2261
2915
  }
2262
2916
  }
2917
+ function renderUploadedVideoPreview(container, thumbnailUrl, state) {
2918
+ const video = document.createElement("video");
2919
+ video.className = "w-full h-full object-contain";
2920
+ video.controls = true;
2921
+ video.preload = "metadata";
2922
+ video.muted = true;
2923
+ video.src = thumbnailUrl;
2924
+ video.appendChild(document.createTextNode(t("videoNotSupported", state)));
2925
+ container.appendChild(video);
2926
+ }
2263
2927
  async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
2928
+ var _a;
2264
2929
  if (!state.config.getThumbnail) {
2265
2930
  setEmptyFileContainer(container, state);
2266
2931
  return;
@@ -2269,11 +2934,12 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
2269
2934
  const thumbnailUrl = await state.config.getThumbnail(resourceId);
2270
2935
  if (thumbnailUrl) {
2271
2936
  clear(container);
2272
- if (meta && meta.type && meta.type.startsWith("video/")) {
2273
- renderUploadedVideoPreview(container, thumbnailUrl, meta.type, state);
2937
+ if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/")) {
2938
+ renderUploadedVideoPreview(container, thumbnailUrl, state);
2274
2939
  } else {
2275
2940
  const img = document.createElement("img");
2276
2941
  img.className = "w-full h-full object-contain";
2942
+ img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
2277
2943
  img.alt = fileName || t("previewAlt", state);
2278
2944
  img.src = thumbnailUrl;
2279
2945
  container.appendChild(img);
@@ -2302,9 +2968,6 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
2302
2968
  );
2303
2969
  }
2304
2970
  clear(container);
2305
- if (isReadonly) {
2306
- container.classList.add("cursor-pointer");
2307
- }
2308
2971
  const meta = state.resourceIndex.get(resourceId);
2309
2972
  if (meta && meta.file && meta.file instanceof File) {
2310
2973
  await renderLocalFilePreview(
@@ -2317,369 +2980,296 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
2317
2980
  deps
2318
2981
  );
2319
2982
  } else {
2320
- await renderUploadedFilePreview(
2321
- container,
2322
- resourceId,
2323
- fileName,
2324
- meta,
2325
- state
2326
- );
2983
+ await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
2327
2984
  const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
2328
2985
  if (!isReadonly && !isVideo) {
2329
2986
  renderDeleteButton(container, resourceId, state);
2330
2987
  }
2331
2988
  }
2332
2989
  }
2333
- async function renderFilePreviewReadonly(resourceId, state, fileName) {
2990
+ function resolveFileName(resourceId, meta, fileName) {
2991
+ var _a;
2992
+ if (fileName) return fileName;
2993
+ if ((_a = meta == null ? void 0 : meta.name) == null ? void 0 : _a.includes(".")) return meta.name;
2994
+ const basename = resourceId.includes("/") ? resourceId.split("/").pop() : resourceId;
2995
+ return (basename == null ? void 0 : basename.includes(".")) ? basename : "";
2996
+ }
2997
+ async function renderFilePreviewReadonly(resourceId, state, fileName, options = {}) {
2334
2998
  var _a, _b;
2335
2999
  const meta = state.resourceIndex.get(resourceId);
2336
- const actualFileName = (meta == null ? void 0 : meta.name) || resourceId.split("/").pop() || "file";
2337
- const isPSD = actualFileName.toLowerCase().match(/\.psd$/);
2338
- const fileResult = document.createElement("div");
2339
- fileResult.className = isPSD ? "space-y-2" : "space-y-3";
2340
- const previewContainer = document.createElement("div");
2341
- if (isPSD) {
2342
- previewContainer.className = "bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:opacity-90 transition-opacity flex items-center p-3 max-w-sm";
2343
- } else {
2344
- previewContainer.className = "bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:opacity-90 transition-opacity";
2345
- }
2346
- const isImage = !isPSD && (((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) || actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/));
2347
- const isVideo = ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) || actualFileName.toLowerCase().match(/\.(mp4|webm|avi|mov)$/);
2348
- if (isImage) {
3000
+ const actualFileName = resolveFileName(resourceId, meta, fileName);
3001
+ const { canRemove = false, removeHandler = null } = options;
3002
+ const isImage = ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) || Boolean(actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/));
3003
+ const isVideo = ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) || Boolean(actualFileName.toLowerCase().match(/\.(mp4|webm|avi|mov)$/));
3004
+ const tile = createFileTile();
3005
+ tile.classList.add("fb-tile-resource");
3006
+ tile.style.cursor = "pointer";
3007
+ if (actualFileName) {
3008
+ tile.title = actualFileName;
3009
+ }
3010
+ const localFileUrl = (meta == null ? void 0 : meta.file) instanceof File ? getLocalFileUrl(meta.file) : null;
3011
+ const resolveOpenUrl = async () => {
3012
+ if (state.config.getDownloadUrl) return state.config.getDownloadUrl(resourceId);
3013
+ if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
3014
+ return localFileUrl;
3015
+ };
3016
+ tile.onclick = async () => {
3017
+ const url = await resolveOpenUrl();
3018
+ if (url) {
3019
+ window.open(url, "_blank");
3020
+ } else if (state.config.downloadFile) {
3021
+ state.config.downloadFile(resourceId, actualFileName);
3022
+ } else {
3023
+ forceDownload(resourceId, actualFileName, state).catch((err) => {
3024
+ console.error("Download failed:", err);
3025
+ });
3026
+ }
3027
+ };
3028
+ const actionsEl = createTileActions({
3029
+ canRemove,
3030
+ removeHandler,
3031
+ state,
3032
+ resourceId,
3033
+ fileName: actualFileName,
3034
+ meta
3035
+ });
3036
+ const resolveImageDisplayUrl = async () => {
2349
3037
  if (state.config.getThumbnail) {
2350
3038
  try {
2351
- const thumbnailUrl = await state.config.getThumbnail(resourceId);
2352
- if (thumbnailUrl) {
2353
- previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${escapeHtml(actualFileName)}" class="w-full h-auto">`;
2354
- } else {
2355
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
2356
- }
2357
- } catch (error) {
2358
- console.warn("getThumbnail failed for", resourceId, error);
2359
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
3039
+ const url = await state.config.getThumbnail(resourceId);
3040
+ if (url) return url;
3041
+ } catch {
2360
3042
  }
3043
+ }
3044
+ return localFileUrl;
3045
+ };
3046
+ const renderImageFallback = () => {
3047
+ tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
3048
+ tile.appendChild(actionsEl);
3049
+ };
3050
+ if (isImage) {
3051
+ const displayUrl = await resolveImageDisplayUrl();
3052
+ if (displayUrl) {
3053
+ const img = document.createElement("img");
3054
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3055
+ img.alt = actualFileName;
3056
+ img.src = displayUrl;
3057
+ tile.appendChild(img);
3058
+ tile.appendChild(actionsEl);
3059
+ attachZoomHover(tile, displayUrl, actualFileName, actionsEl);
2361
3060
  } else {
2362
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
3061
+ renderImageFallback();
2363
3062
  }
2364
3063
  } else if (isVideo) {
2365
3064
  if (state.config.getThumbnail) {
2366
3065
  try {
2367
3066
  const videoUrl = await state.config.getThumbnail(resourceId);
2368
3067
  if (videoUrl) {
2369
- previewContainer.innerHTML = `
2370
- <div class="relative group">
2371
- <video class="w-full h-auto" controls preload="auto" muted src="${videoUrl}">
2372
- ${escapeHtml(t("videoNotSupported", state))}
2373
- </video>
2374
- <div class="absolute inset-0 bg-black bg-opacity-20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
2375
- <div class="bg-white bg-opacity-90 rounded-full p-3">
2376
- <svg class="w-8 h-8 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
2377
- <path d="M8 5v14l11-7z"/>
2378
- </svg>
2379
- </div>
3068
+ tile.innerHTML = `
3069
+ <img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(actualFileName)}" src="${videoUrl}">
3070
+ <div class="fb-video-overlay">
3071
+ <div class="fb-play-btn" style="width:22px;height:22px;">
3072
+ <svg width="10" height="12" viewBox="0 0 10 12" fill="currentColor"><path d="M0 0l10 6-10 6z"/></svg>
2380
3073
  </div>
2381
- </div>
2382
- `;
3074
+ </div>`;
3075
+ tile.appendChild(actionsEl);
2383
3076
  } else {
2384
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
3077
+ tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
3078
+ tile.appendChild(actionsEl);
2385
3079
  }
2386
- } catch (error) {
2387
- console.warn("getThumbnail failed for video", resourceId, error);
2388
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
3080
+ } catch {
3081
+ tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
3082
+ tile.appendChild(actionsEl);
2389
3083
  }
2390
3084
  } else {
2391
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
3085
+ tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
3086
+ tile.appendChild(actionsEl);
2392
3087
  }
2393
3088
  } else {
2394
- const fileIcon = isPSD ? "\u{1F3A8}" : "\u{1F4C1}";
2395
- const fileDescription = isPSD ? "PSD File" : "Document";
2396
- if (isPSD) {
2397
- previewContainer.innerHTML = `
2398
- <div class="flex items-center space-x-3">
2399
- <div class="text-3xl text-gray-400">${fileIcon}</div>
2400
- <div class="flex-1 min-w-0">
2401
- <div class="text-sm font-medium text-gray-900 truncate">${escapeHtml(actualFileName)}</div>
2402
- <div class="text-xs text-gray-500">${fileDescription}</div>
2403
- </div>
2404
- </div>
2405
- `;
2406
- } else {
2407
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">${fileIcon}</div><div class="text-sm">${escapeHtml(actualFileName)}</div><div class="text-xs text-gray-500 mt-1">${fileDescription}</div></div></div>`;
3089
+ if (state.config.getThumbnail) {
3090
+ try {
3091
+ const thumbUrl = await state.config.getThumbnail(resourceId);
3092
+ if (thumbUrl) {
3093
+ const img = document.createElement("img");
3094
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3095
+ img.alt = actualFileName || resourceId;
3096
+ img.src = thumbUrl;
3097
+ tile.appendChild(img);
3098
+ tile.appendChild(actionsEl);
3099
+ return tile;
3100
+ }
3101
+ } catch {
3102
+ }
2408
3103
  }
3104
+ const captionHtml = actualFileName ? `<div class="fb-tile-label">${escapeHtml(actualFileName.length > 10 ? actualFileName.substring(0, 8) + "\u2026" : actualFileName)}</div>
3105
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zm-7 9H5v2h14v-2h-7z"/></svg>` : "";
3106
+ tile.innerHTML = `
3107
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
3108
+ <div style="font-size:36px;">\u{1F4C1}</div>
3109
+ ${captionHtml}
3110
+ </div>`;
3111
+ tile.appendChild(actionsEl);
2409
3112
  }
2410
- const fileNameElement = document.createElement("p");
2411
- fileNameElement.className = isPSD ? "hidden" : "text-sm font-medium text-gray-900 text-center";
2412
- fileNameElement.textContent = actualFileName;
2413
- const downloadButton = document.createElement("button");
2414
- downloadButton.className = "w-full px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors";
2415
- downloadButton.textContent = t("downloadButton", state);
2416
- downloadButton.onclick = (e) => {
2417
- e.preventDefault();
2418
- e.stopPropagation();
2419
- if (state.config.downloadFile) {
2420
- state.config.downloadFile(resourceId, actualFileName);
2421
- } else {
2422
- forceDownload(resourceId, actualFileName, state);
2423
- }
2424
- };
2425
- fileResult.appendChild(previewContainer);
2426
- fileResult.appendChild(fileNameElement);
2427
- fileResult.appendChild(downloadButton);
2428
- return fileResult;
2429
- }
2430
- function renderResourcePills(container, rids, state, onRemove, hint, countInfo) {
2431
- clear(container);
2432
- const buildHintLine = () => {
2433
- const parts = [t("clickDragTextMultiple", state)];
2434
- if (hint) parts.push(hint);
2435
- if (countInfo) parts.push(countInfo);
2436
- return parts.join(" \u2022 ");
2437
- };
2438
- const isInitialRender = !container.classList.contains("grid");
2439
- if ((!rids || rids.length === 0) && isInitialRender) {
2440
- const gridContainer2 = document.createElement("div");
2441
- gridContainer2.className = "grid grid-cols-4 gap-3 mb-3";
2442
- for (let i = 0; i < 4; i++) {
2443
- const slot = document.createElement("div");
2444
- slot.className = "aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
2445
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2446
- svg.setAttribute("class", "w-12 h-12 text-gray-400");
2447
- svg.setAttribute("fill", "currentColor");
2448
- svg.setAttribute("viewBox", "0 0 24 24");
2449
- const path = document.createElementNS(
2450
- "http://www.w3.org/2000/svg",
2451
- "path"
2452
- );
2453
- path.setAttribute(
2454
- "d",
2455
- "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"
2456
- );
2457
- svg.appendChild(path);
2458
- slot.appendChild(svg);
2459
- slot.onclick = () => {
2460
- let filesWrapper = container.parentElement;
2461
- while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
2462
- filesWrapper = filesWrapper.parentElement;
2463
- }
2464
- if (!filesWrapper && container.classList.contains("space-y-2")) {
2465
- filesWrapper = container;
2466
- }
2467
- const fileInput = filesWrapper == null ? void 0 : filesWrapper.querySelector(
2468
- 'input[type="file"]'
2469
- );
2470
- if (fileInput) fileInput.click();
2471
- };
2472
- gridContainer2.appendChild(slot);
2473
- }
2474
- const hintText2 = document.createElement("div");
2475
- hintText2.className = "text-center text-xs text-gray-500 mt-2";
2476
- hintText2.textContent = buildHintLine();
2477
- container.appendChild(gridContainer2);
2478
- container.appendChild(hintText2);
2479
- return;
2480
- }
2481
- const gridContainer = document.createElement("div");
2482
- gridContainer.className = "files-list grid grid-cols-4 gap-3";
2483
- const currentImagesCount = rids ? rids.length : 0;
2484
- const rowsNeeded = Math.floor(currentImagesCount / 4) + 1;
2485
- const slotsNeeded = rowsNeeded * 4;
2486
- for (let i = 0; i < slotsNeeded; i++) {
2487
- const slot = document.createElement("div");
2488
- if (rids && i < rids.length) {
2489
- const rid = rids[i];
2490
- const meta = state.resourceIndex.get(rid);
2491
- slot.className = "resource-pill aspect-square bg-gray-100 rounded-lg overflow-hidden relative group border border-gray-300";
2492
- slot.dataset.resourceId = rid;
2493
- renderThumbnailForResource(slot, rid, meta, state).catch((err) => {
2494
- console.error("Failed to render thumbnail:", err);
2495
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2496
- <div class="text-2xl mb-1">\u{1F4C1}</div>
2497
- <div class="text-xs">${escapeHtml(t("previewError", state))}</div>
2498
- </div>`;
2499
- });
2500
- if (onRemove) {
2501
- const overlay = document.createElement("div");
2502
- overlay.className = "absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center";
2503
- const removeBtn = document.createElement("button");
2504
- removeBtn.className = "bg-red-600 text-white px-2 py-1 rounded text-xs";
2505
- removeBtn.textContent = t("removeElement", state);
2506
- removeBtn.onclick = (e) => {
2507
- e.stopPropagation();
2508
- onRemove(rid);
2509
- };
2510
- overlay.appendChild(removeBtn);
2511
- slot.appendChild(overlay);
2512
- }
2513
- } else {
2514
- slot.className = "aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
2515
- slot.innerHTML = '<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>';
2516
- slot.onclick = () => {
2517
- let filesWrapper = container.parentElement;
2518
- while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
2519
- filesWrapper = filesWrapper.parentElement;
2520
- }
2521
- if (!filesWrapper && container.classList.contains("space-y-2")) {
2522
- filesWrapper = container;
2523
- }
2524
- const fileInput = filesWrapper == null ? void 0 : filesWrapper.querySelector(
2525
- 'input[type="file"]'
2526
- );
2527
- if (fileInput) fileInput.click();
2528
- };
2529
- }
2530
- gridContainer.appendChild(slot);
2531
- }
2532
- container.appendChild(gridContainer);
2533
- const hintText = document.createElement("div");
2534
- hintText.className = "text-center text-xs text-gray-500 mt-2";
2535
- hintText.textContent = buildHintLine();
2536
- container.appendChild(hintText);
3113
+ return tile;
2537
3114
  }
2538
- function renderThumbnailError(slot, state, iconSize = "w-12 h-12") {
2539
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2540
- <svg class="${escapeHtml(iconSize)} text-red-400" fill="currentColor" viewBox="0 0 24 24">
2541
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
2542
- </svg>
2543
- <div class="text-xs mt-1 text-red-600">${escapeHtml(t("previewError", state))}</div>
2544
- </div>`;
3115
+ async function renderSingleFileEditTile(fileContainer, resourceId, state, deps) {
3116
+ var _a, _b, _c;
3117
+ const meta = state.resourceIndex.get(resourceId);
3118
+ const fileName = (_b = (_a = meta == null ? void 0 : meta.name) != null ? _a : resourceId.split("/").pop()) != null ? _b : "";
3119
+ const removeHandler = (_c = deps.onRemove) != null ? _c : null;
3120
+ const tile = await renderFilePreviewReadonly(resourceId, state, fileName, {
3121
+ canRemove: true,
3122
+ removeHandler
3123
+ });
3124
+ fileContainer.className = "file-preview-container";
3125
+ fileContainer.removeAttribute("style");
3126
+ clear(fileContainer);
3127
+ fileContainer.appendChild(tile);
2545
3128
  }
2546
- async function renderThumbnailForResource(slot, rid, meta, state) {
2547
- var _a, _b;
2548
- if (meta && ((_a = meta.type) == null ? void 0 : _a.startsWith("image/"))) {
3129
+ async function fillTileContent(tile, rid, meta, state, actionsEl) {
3130
+ var _a, _b, _c;
3131
+ if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) {
2549
3132
  if (meta.file && meta.file instanceof File) {
2550
3133
  const img = document.createElement("img");
2551
- img.className = "w-full h-full object-contain";
3134
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
2552
3135
  img.alt = meta.name;
2553
3136
  const reader = new FileReader();
2554
3137
  reader.onload = (e) => {
2555
3138
  var _a2;
2556
3139
  img.src = ((_a2 = e.target) == null ? void 0 : _a2.result) || "";
3140
+ attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
2557
3141
  };
2558
3142
  reader.readAsDataURL(meta.file);
2559
- slot.appendChild(img);
3143
+ tile.appendChild(img);
2560
3144
  } else if (state.config.getThumbnail) {
2561
3145
  try {
2562
3146
  const url = await state.config.getThumbnail(rid);
2563
3147
  if (url) {
2564
3148
  const img = document.createElement("img");
2565
- img.className = "w-full h-full object-contain";
3149
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
2566
3150
  img.alt = meta.name;
2567
3151
  img.src = url;
2568
- slot.appendChild(img);
3152
+ tile.appendChild(img);
3153
+ attachZoomHover(tile, url, meta.name, actionsEl != null ? actionsEl : null);
2569
3154
  } else {
2570
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2571
- <svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
2572
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
2573
- </svg>
2574
- </div>`;
3155
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
2575
3156
  }
2576
3157
  } catch (error) {
2577
3158
  const err = error instanceof Error ? error : new Error(String(error));
2578
- if (state.config.onThumbnailError) {
2579
- state.config.onThumbnailError(err, rid);
2580
- }
2581
- renderThumbnailError(slot, state);
3159
+ if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
3160
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
2582
3161
  }
2583
3162
  } else {
2584
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2585
- <svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
2586
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
2587
- </svg>
2588
- </div>`;
3163
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
2589
3164
  }
2590
- } else if (meta && ((_b = meta.type) == null ? void 0 : _b.startsWith("video/"))) {
3165
+ if (actionsEl) tile.appendChild(actionsEl);
3166
+ } else if ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) {
2591
3167
  if (meta.file && meta.file instanceof File) {
2592
3168
  const videoUrl = URL.createObjectURL(meta.file);
2593
- slot.innerHTML = `
2594
- <div class="relative group h-full w-full">
2595
- <video class="w-full h-full object-contain" preload="metadata" muted src="${videoUrl}">
2596
- </video>
2597
- <div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
2598
- <div class="bg-white bg-opacity-90 rounded-full p-1">
2599
- <svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
2600
- <path d="M8 5v14l11-7z"/>
2601
- </svg>
2602
- </div>
3169
+ tile.innerHTML = `
3170
+ <video style="width:100%;height:100%;" preload="metadata" muted src="${videoUrl}"></video>
3171
+ <div class="fb-video-overlay">
3172
+ <div class="fb-play-btn" style="width:20px;height:20px;">
3173
+ <svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
2603
3174
  </div>
2604
- </div>
2605
- `;
3175
+ </div>`;
2606
3176
  } else if (state.config.getThumbnail) {
2607
3177
  try {
2608
3178
  const videoUrl = await state.config.getThumbnail(rid);
2609
3179
  if (videoUrl) {
2610
- slot.innerHTML = `
2611
- <div class="relative group h-full w-full">
2612
- <video class="w-full h-full object-contain" preload="metadata" muted src="${videoUrl}">
2613
- </video>
2614
- <div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
2615
- <div class="bg-white bg-opacity-90 rounded-full p-1">
2616
- <svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
2617
- <path d="M8 5v14l11-7z"/>
2618
- </svg>
2619
- </div>
3180
+ tile.innerHTML = `
3181
+ <img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(meta.name)}" src="${videoUrl}">
3182
+ <div class="fb-video-overlay">
3183
+ <div class="fb-play-btn" style="width:20px;height:20px;">
3184
+ <svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
2620
3185
  </div>
2621
- </div>
2622
- `;
3186
+ </div>`;
2623
3187
  } else {
2624
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2625
- <svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
2626
- <path d="M8 5v14l11-7z"/>
2627
- </svg>
2628
- <div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
2629
- </div>`;
3188
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
2630
3189
  }
2631
3190
  } catch (error) {
2632
3191
  const err = error instanceof Error ? error : new Error(String(error));
2633
- if (state.config.onThumbnailError) {
2634
- state.config.onThumbnailError(err, rid);
2635
- }
2636
- renderThumbnailError(slot, state, "w-8 h-8");
3192
+ if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
3193
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
2637
3194
  }
2638
3195
  } else {
2639
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2640
- <svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
2641
- <path d="M8 5v14l11-7z"/>
2642
- </svg>
2643
- <div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
2644
- </div>`;
3196
+ tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
2645
3197
  }
3198
+ if (actionsEl) tile.appendChild(actionsEl);
2646
3199
  } else {
2647
- slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
2648
- <div class="text-2xl mb-1">\u{1F4C1}</div>
2649
- <div class="text-xs">${escapeHtml((meta == null ? void 0 : meta.name) || "File")}</div>
2650
- </div>`;
3200
+ const name = (_c = meta == null ? void 0 : meta.name) != null ? _c : "";
3201
+ const hasExtension = name.includes(".");
3202
+ const captionHtml = hasExtension ? `<div class="fb-tile-label">${escapeHtml(name.length > 10 ? name.substring(0, 8) + "\u2026" : name)}</div>` : "";
3203
+ tile.innerHTML = `
3204
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
3205
+ <div style="font-size:36px;">\u{1F4C1}</div>
3206
+ ${captionHtml}
3207
+ </div>`;
3208
+ if (actionsEl) tile.appendChild(actionsEl);
2651
3209
  }
2652
3210
  }
2653
- function setEmptyFileContainer(fileContainer, state, hint) {
2654
- const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
2655
- fileContainer.innerHTML = `
2656
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
2657
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
2658
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
2659
- </svg>
2660
- <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
2661
- ${hintHtml}
2662
- </div>
2663
- `;
3211
+ async function forceDownload(resourceId, fileName, state) {
3212
+ try {
3213
+ let fileUrl = null;
3214
+ if (state.config.getDownloadUrl) {
3215
+ fileUrl = state.config.getDownloadUrl(resourceId);
3216
+ } else if (state.config.getThumbnail) {
3217
+ fileUrl = await state.config.getThumbnail(resourceId);
3218
+ }
3219
+ if (fileUrl) {
3220
+ const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
3221
+ const response = await fetch(finalUrl);
3222
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
3223
+ const blob = await response.blob();
3224
+ downloadBlob(blob, fileName);
3225
+ } else {
3226
+ throw new Error("No download URL available for resource");
3227
+ }
3228
+ } catch (error) {
3229
+ const err = error instanceof Error ? error : new Error(String(error));
3230
+ if (state.config.onDownloadError) {
3231
+ state.config.onDownloadError(err, resourceId, fileName);
3232
+ }
3233
+ console.error(`File download failed for ${fileName}:`, err);
3234
+ throw err;
3235
+ }
2664
3236
  }
2665
- function showFileError(container, message) {
2666
- var _a, _b;
2667
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2668
- if (existing) existing.remove();
2669
- const errorEl = document.createElement("div");
2670
- errorEl.className = "file-error-message error-message";
2671
- errorEl.style.cssText = `
2672
- color: var(--fb-error-color);
2673
- font-size: var(--fb-font-size-small);
2674
- margin-top: 0.25rem;
2675
- `;
2676
- errorEl.textContent = message;
2677
- (_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
3237
+ function downloadBlob(blob, fileName) {
3238
+ try {
3239
+ const blobUrl = URL.createObjectURL(blob);
3240
+ const link = document.createElement("a");
3241
+ link.href = blobUrl;
3242
+ link.download = fileName;
3243
+ link.style.display = "none";
3244
+ document.body.appendChild(link);
3245
+ link.click();
3246
+ document.body.removeChild(link);
3247
+ setTimeout(() => {
3248
+ URL.revokeObjectURL(blobUrl);
3249
+ }, 100);
3250
+ } catch (error) {
3251
+ throw new Error(`Blob download failed: ${error.message}`);
3252
+ }
2678
3253
  }
2679
- function clearFileError(container) {
2680
- var _a;
2681
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2682
- if (existing) existing.remove();
3254
+
3255
+ // src/components/file/upload.ts
3256
+ async function uploadSingleFile(file, state) {
3257
+ if (!state.config.uploadFile) {
3258
+ throw new Error(
3259
+ "No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
3260
+ );
3261
+ }
3262
+ try {
3263
+ const rid = await state.config.uploadFile(file);
3264
+ if (typeof rid !== "string") {
3265
+ throw new Error("Upload handler must return a string resource ID");
3266
+ }
3267
+ return rid;
3268
+ } catch (error) {
3269
+ const err = error instanceof Error ? error : new Error(String(error));
3270
+ if (state.config.onUploadError) state.config.onUploadError(err, file);
3271
+ throw new Error(`File upload failed: ${err.message}`);
3272
+ }
2683
3273
  }
2684
3274
  async function handleFileSelect(file, container, fieldName, state, deps = null, instance, allowedExtensions = [], maxSizeMB = Infinity) {
2685
3275
  var _a, _b;
@@ -2699,24 +3289,18 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
2699
3289
  return;
2700
3290
  }
2701
3291
  clearFileError(container);
3292
+ ensureFileStyles();
3293
+ container.innerHTML = `
3294
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
3295
+ <div class="fb-spinner"></div>
3296
+ <div style="font-size:11px;color:var(--fb-text-secondary-color,#6b7280);text-align:center;">${escapeHtml(t("uploadingFile", state))}</div>
3297
+ </div>`;
2702
3298
  let rid;
2703
- if (state.config.uploadFile) {
2704
- try {
2705
- rid = await state.config.uploadFile(file);
2706
- if (typeof rid !== "string") {
2707
- throw new Error("Upload handler must return a string resource ID");
2708
- }
2709
- } catch (error) {
2710
- const err = error instanceof Error ? error : new Error(String(error));
2711
- if (state.config.onUploadError) {
2712
- state.config.onUploadError(err, file);
2713
- }
2714
- throw new Error(`File upload failed: ${err.message}`);
2715
- }
2716
- } else {
2717
- throw new Error(
2718
- "No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
2719
- );
3299
+ try {
3300
+ rid = await uploadSingleFile(file, state);
3301
+ } catch (error) {
3302
+ setEmptyFileContainer(container, state);
3303
+ throw error;
2720
3304
  }
2721
3305
  state.resourceIndex.set(rid, {
2722
3306
  name: file.name,
@@ -2724,7 +3308,6 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
2724
3308
  size: file.size,
2725
3309
  uploadedAt: /* @__PURE__ */ new Date(),
2726
3310
  file
2727
- // Store the file object for local preview
2728
3311
  });
2729
3312
  let hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
2730
3313
  'input[type="hidden"]'
@@ -2736,144 +3319,135 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
2736
3319
  (_b = container.parentElement) == null ? void 0 : _b.appendChild(hiddenInput);
2737
3320
  }
2738
3321
  hiddenInput.value = rid;
2739
- renderFilePreview(container, rid, state, {
2740
- fileName: file.name,
2741
- isReadonly: false,
2742
- deps
2743
- }).catch(console.error);
3322
+ const isVideo = file.type.startsWith("video/");
3323
+ if (!isVideo && deps) {
3324
+ renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
3325
+ } else {
3326
+ renderFilePreview(container, rid, state, {
3327
+ fileName: file.name,
3328
+ isReadonly: false,
3329
+ deps
3330
+ }).catch(console.error);
3331
+ }
2744
3332
  if (instance && !state.config.readonly) {
2745
3333
  instance.triggerOnChange(fieldName, rid);
2746
3334
  }
2747
3335
  }
2748
- function setupDragAndDrop(element, dropHandler) {
2749
- element.addEventListener("dragover", (e) => {
2750
- e.preventDefault();
2751
- element.classList.add("border-blue-500", "bg-blue-50");
2752
- });
2753
- element.addEventListener("dragleave", (e) => {
2754
- e.preventDefault();
2755
- element.classList.remove("border-blue-500", "bg-blue-50");
2756
- });
2757
- element.addEventListener("drop", (e) => {
2758
- var _a;
2759
- e.preventDefault();
2760
- element.classList.remove("border-blue-500", "bg-blue-50");
2761
- if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
2762
- dropHandler(e.dataTransfer.files);
2763
- }
2764
- });
2765
- }
2766
- function addDeleteButton(container, state, onDelete) {
2767
- const existingOverlay = container.querySelector(".delete-overlay");
2768
- if (existingOverlay) {
2769
- existingOverlay.remove();
2770
- }
2771
- const overlay = document.createElement("div");
2772
- overlay.className = "delete-overlay absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center";
2773
- const deleteBtn = document.createElement("button");
2774
- deleteBtn.className = "bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors";
2775
- deleteBtn.textContent = t("removeElement", state);
2776
- deleteBtn.onclick = (e) => {
2777
- e.stopPropagation();
2778
- onDelete();
2779
- };
2780
- overlay.appendChild(deleteBtn);
2781
- container.appendChild(overlay);
2782
- }
2783
- async function uploadSingleFile(file, state) {
2784
- if (state.config.uploadFile) {
2785
- try {
2786
- const rid = await state.config.uploadFile(file);
2787
- if (typeof rid !== "string") {
2788
- throw new Error("Upload handler must return a string resource ID");
2789
- }
2790
- return rid;
2791
- } catch (error) {
2792
- const err = error instanceof Error ? error : new Error(String(error));
2793
- if (state.config.onUploadError) {
2794
- state.config.onUploadError(err, file);
2795
- }
2796
- throw new Error(`File upload failed: ${err.message}`);
2797
- }
2798
- } else {
2799
- throw new Error(
2800
- "No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
3336
+ function filterAndSlice(allFiles, currentCount, constraints, state) {
3337
+ const rejectedByExt = allFiles.filter(
3338
+ (f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
3339
+ );
3340
+ const afterExt = allFiles.filter(
3341
+ (f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
3342
+ );
3343
+ const rejectedBySize = afterExt.filter(
3344
+ (f) => !isFileSizeAllowed(f, constraints.maxSize)
3345
+ );
3346
+ const valid = afterExt.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
3347
+ const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
3348
+ const accepted = valid.slice(0, remaining);
3349
+ const skippedByCount = valid.length - accepted.length;
3350
+ const errorParts = [];
3351
+ if (rejectedByExt.length > 0) {
3352
+ const formats = constraints.allowedExtensions.join(", ");
3353
+ const names = rejectedByExt.map((f) => f.name).join(", ");
3354
+ errorParts.push(t("invalidFileExtension", state, { name: names, formats }));
3355
+ }
3356
+ if (rejectedBySize.length > 0) {
3357
+ const names = rejectedBySize.map((f) => f.name).join(", ");
3358
+ errorParts.push(
3359
+ t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
2801
3360
  );
2802
3361
  }
2803
- }
2804
- async function forceDownload(resourceId, fileName, state) {
2805
- try {
2806
- let fileUrl = null;
2807
- if (state.config.getDownloadUrl) {
2808
- fileUrl = state.config.getDownloadUrl(resourceId);
2809
- } else if (state.config.getThumbnail) {
2810
- fileUrl = await state.config.getThumbnail(resourceId);
2811
- }
2812
- if (fileUrl) {
2813
- const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
2814
- const response = await fetch(finalUrl);
2815
- if (!response.ok) {
2816
- throw new Error(`HTTP error! status: ${response.status}`);
2817
- }
2818
- const blob = await response.blob();
2819
- downloadBlob(blob, fileName);
2820
- } else {
2821
- throw new Error("No download URL available for resource");
2822
- }
2823
- } catch (error) {
2824
- const err = error instanceof Error ? error : new Error(String(error));
2825
- if (state.config.onDownloadError) {
2826
- state.config.onDownloadError(err, resourceId, fileName);
2827
- }
2828
- console.error(`File download failed for ${fileName}:`, err);
2829
- throw err;
2830
- }
2831
- }
2832
- function downloadBlob(blob, fileName) {
2833
- try {
2834
- const blobUrl = URL.createObjectURL(blob);
2835
- const link = document.createElement("a");
2836
- link.href = blobUrl;
2837
- link.download = fileName;
2838
- link.style.display = "none";
2839
- document.body.appendChild(link);
2840
- link.click();
2841
- document.body.removeChild(link);
2842
- setTimeout(() => {
2843
- URL.revokeObjectURL(blobUrl);
2844
- }, 100);
2845
- } catch (error) {
2846
- throw new Error(`Blob download failed: ${error.message}`);
3362
+ if (skippedByCount > 0) {
3363
+ errorParts.push(
3364
+ t("filesLimitExceeded", state, {
3365
+ skipped: skippedByCount,
3366
+ max: constraints.maxCount
3367
+ })
3368
+ );
2847
3369
  }
2848
- }
2849
- function addPrefillFilesToIndex(initialFiles, state) {
2850
- if (initialFiles.length > 0) {
2851
- initialFiles.forEach((resourceId) => {
2852
- var _a;
2853
- if (!state.resourceIndex.has(resourceId)) {
2854
- const filename = resourceId.split("/").pop() || "file";
2855
- const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
2856
- let fileType = "application/octet-stream";
2857
- if (extension) {
2858
- if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
2859
- fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
2860
- } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
2861
- fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
2862
- }
3370
+ return { accepted, errorMessage: errorParts.join(" \u2022 ") };
3371
+ }
3372
+ async function uploadBatch(accepted, resourceIds, listEl, state) {
3373
+ await Promise.all(
3374
+ accepted.map(async (file) => {
3375
+ const placeholder = createUploadingTile(file.name, state);
3376
+ if (listEl) {
3377
+ const tilesWrap = ensureTilesWrap(listEl);
3378
+ const addTile = tilesWrap.querySelector(".fb-tile-add");
3379
+ if (addTile) {
3380
+ tilesWrap.insertBefore(placeholder, addTile);
3381
+ } else {
3382
+ tilesWrap.appendChild(placeholder);
2863
3383
  }
2864
- state.resourceIndex.set(resourceId, {
2865
- name: filename,
2866
- type: fileType,
2867
- size: 0,
3384
+ }
3385
+ try {
3386
+ const rid = await uploadSingleFile(file, state);
3387
+ state.resourceIndex.set(rid, {
3388
+ name: file.name,
3389
+ type: file.type,
3390
+ size: file.size,
2868
3391
  uploadedAt: /* @__PURE__ */ new Date(),
2869
3392
  file: void 0
2870
3393
  });
3394
+ resourceIds.push(rid);
3395
+ } finally {
3396
+ placeholder.remove();
2871
3397
  }
2872
- });
2873
- }
3398
+ })
3399
+ );
2874
3400
  }
3401
+ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
3402
+ setupDragAndDrop(filesContainer, async (files) => {
3403
+ var _a;
3404
+ const { accepted, errorMessage } = filterAndSlice(
3405
+ Array.from(files),
3406
+ resourceIds.length,
3407
+ constraints,
3408
+ state
3409
+ );
3410
+ if (errorMessage) {
3411
+ showFileError(filesContainer, errorMessage);
3412
+ } else {
3413
+ clearFileError(filesContainer);
3414
+ }
3415
+ const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
3416
+ await uploadBatch(accepted, resourceIds, list, state);
3417
+ updateCallback();
3418
+ if (instance && pathKey && !state.config.readonly) {
3419
+ instance.triggerOnChange(pathKey, resourceIds);
3420
+ }
3421
+ });
3422
+ }
3423
+ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
3424
+ filesPicker.onchange = async () => {
3425
+ if (!filesPicker.files) return;
3426
+ const wrapperEl = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
3427
+ const { accepted, errorMessage } = filterAndSlice(
3428
+ Array.from(filesPicker.files),
3429
+ resourceIds.length,
3430
+ constraints,
3431
+ state
3432
+ );
3433
+ if (errorMessage && wrapperEl) {
3434
+ showFileError(wrapperEl, errorMessage);
3435
+ } else if (wrapperEl) {
3436
+ clearFileError(wrapperEl);
3437
+ }
3438
+ const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
3439
+ await uploadBatch(accepted, resourceIds, listEl != null ? listEl : null, state);
3440
+ updateCallback();
3441
+ filesPicker.value = "";
3442
+ if (instance && pathKey && !state.config.readonly) {
3443
+ instance.triggerOnChange(pathKey, resourceIds);
3444
+ }
3445
+ };
3446
+ }
3447
+
3448
+ // src/components/file/render-edit.ts
2875
3449
  function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
2876
- var _a;
3450
+ var _a, _b;
2877
3451
  if (!state.resourceIndex.has(initial)) {
2878
3452
  const filename = initial.split("/").pop() || "file";
2879
3453
  const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
@@ -2893,501 +3467,564 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
2893
3467
  file: void 0
2894
3468
  });
2895
3469
  }
2896
- renderFilePreview(fileContainer, initial, state, {
2897
- fileName: initial,
2898
- isReadonly: false,
2899
- deps
2900
- }).catch(console.error);
3470
+ const meta = state.resourceIndex.get(initial);
3471
+ const isVideo = (_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/");
3472
+ if (isVideo) {
3473
+ renderFilePreview(fileContainer, initial, state, {
3474
+ fileName: initial,
3475
+ isReadonly: false,
3476
+ deps
3477
+ }).catch(console.error);
3478
+ } else {
3479
+ renderSingleFileEditTile(fileContainer, initial, state, deps).catch(console.error);
3480
+ }
2901
3481
  const hiddenInput = document.createElement("input");
2902
3482
  hiddenInput.type = "hidden";
2903
3483
  hiddenInput.name = pathKey;
2904
3484
  hiddenInput.value = initial;
2905
3485
  fileWrapper.appendChild(hiddenInput);
2906
3486
  }
2907
- function setupFilesDropHandler(filesContainer, initialFiles, state, updateCallback, constraints, pathKey, instance) {
2908
- setupDragAndDrop(filesContainer, async (files) => {
2909
- const allFiles = Array.from(files);
2910
- const rejectedByExtension = allFiles.filter(
2911
- (f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
2912
- );
2913
- const afterExtension = allFiles.filter(
2914
- (f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
2915
- );
2916
- const rejectedBySize = afterExtension.filter(
2917
- (f) => !isFileSizeAllowed(f, constraints.maxSize)
2918
- );
2919
- const validFiles = afterExtension.filter(
2920
- (f) => isFileSizeAllowed(f, constraints.maxSize)
2921
- );
2922
- const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
2923
- const arr = validFiles.slice(0, remaining);
2924
- const skippedByCount = validFiles.length - arr.length;
2925
- const errorParts = [];
2926
- if (rejectedByExtension.length > 0) {
2927
- const formats = constraints.allowedExtensions.join(", ");
2928
- const names = rejectedByExtension.map((f) => f.name).join(", ");
2929
- errorParts.push(
2930
- t("invalidFileExtension", state, { name: names, formats })
2931
- );
2932
- }
2933
- if (rejectedBySize.length > 0) {
2934
- const names = rejectedBySize.map((f) => f.name).join(", ");
2935
- errorParts.push(
2936
- t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
2937
- );
2938
- }
2939
- if (skippedByCount > 0) {
2940
- errorParts.push(
2941
- t("filesLimitExceeded", state, {
2942
- skipped: skippedByCount,
2943
- max: constraints.maxCount
2944
- })
2945
- );
2946
- }
2947
- if (errorParts.length > 0) {
2948
- showFileError(filesContainer, errorParts.join(" \u2022 "));
3487
+ function renderResourcePills(container, rids, state, onRemove, hint, countInfo, maxCount, isReadonly = false) {
3488
+ var _a;
3489
+ ensureFileStyles();
3490
+ const wrapper = container.closest("[data-files-wrapper]");
3491
+ if (wrapper) {
3492
+ wrapper.dataset.resourceIds = JSON.stringify(rids != null ? rids : []);
3493
+ }
3494
+ while (container.firstChild) container.removeChild(container.firstChild);
3495
+ const ridList = rids != null ? rids : [];
3496
+ const atMax = maxCount !== void 0 && ridList.length >= maxCount;
3497
+ const buildSubHint = () => {
3498
+ const parts = [];
3499
+ if (hint) parts.push(hint);
3500
+ if (countInfo) parts.push(countInfo);
3501
+ return parts.join(" \u2022 ");
3502
+ };
3503
+ const openPicker = () => {
3504
+ const picker = findFilePicker(container);
3505
+ if (picker) picker.click();
3506
+ };
3507
+ if (ridList.length === 0) {
3508
+ if (isReadonly) {
3509
+ const emptyEl = document.createElement("div");
3510
+ emptyEl.className = "fb-tile-empty-text";
3511
+ emptyEl.textContent = t("noFilesSelected", state);
3512
+ container.appendChild(emptyEl);
2949
3513
  } else {
2950
- clearFileError(filesContainer);
2951
- }
2952
- for (const file of arr) {
2953
- const rid = await uploadSingleFile(file, state);
2954
- state.resourceIndex.set(rid, {
2955
- name: file.name,
2956
- type: file.type,
2957
- size: file.size,
2958
- uploadedAt: /* @__PURE__ */ new Date(),
2959
- file: void 0
2960
- });
2961
- initialFiles.push(rid);
2962
- }
2963
- updateCallback();
2964
- if (instance && pathKey && !state.config.readonly) {
2965
- instance.triggerOnChange(pathKey, initialFiles);
2966
- }
2967
- });
2968
- }
2969
- function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallback, constraints, pathKey, instance) {
2970
- filesPicker.onchange = async () => {
2971
- if (filesPicker.files) {
2972
- const allFiles = Array.from(filesPicker.files);
2973
- const rejectedByExtension = allFiles.filter(
2974
- (f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
2975
- );
2976
- const afterExtension = allFiles.filter(
2977
- (f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
2978
- );
2979
- const rejectedBySize = afterExtension.filter(
2980
- (f) => !isFileSizeAllowed(f, constraints.maxSize)
2981
- );
2982
- const validFiles = afterExtension.filter(
2983
- (f) => isFileSizeAllowed(f, constraints.maxSize)
2984
- );
2985
- const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
2986
- const arr = validFiles.slice(0, remaining);
2987
- const skippedByCount = validFiles.length - arr.length;
2988
- const errorParts = [];
2989
- if (rejectedByExtension.length > 0) {
2990
- const formats = constraints.allowedExtensions.join(", ");
2991
- const names = rejectedByExtension.map((f) => f.name).join(", ");
2992
- errorParts.push(
2993
- t("invalidFileExtension", state, { name: names, formats })
2994
- );
2995
- }
2996
- if (rejectedBySize.length > 0) {
2997
- const names = rejectedBySize.map((f) => f.name).join(", ");
2998
- errorParts.push(
2999
- t("fileTooLarge", state, {
3000
- name: names,
3001
- maxSize: constraints.maxSize
3002
- })
3003
- );
3004
- }
3005
- if (skippedByCount > 0) {
3006
- errorParts.push(
3007
- t("filesLimitExceeded", state, {
3008
- skipped: skippedByCount,
3009
- max: constraints.maxCount
3010
- })
3011
- );
3012
- }
3013
- const wrapper = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
3014
- if (errorParts.length > 0 && wrapper) {
3015
- showFileError(wrapper, errorParts.join(" \u2022 "));
3016
- } else if (wrapper) {
3017
- clearFileError(wrapper);
3018
- }
3019
- for (const file of arr) {
3020
- const rid = await uploadSingleFile(file, state);
3021
- state.resourceIndex.set(rid, {
3022
- name: file.name,
3023
- type: file.type,
3024
- size: file.size,
3025
- uploadedAt: /* @__PURE__ */ new Date(),
3026
- file: void 0
3027
- });
3028
- initialFiles.push(rid);
3029
- }
3030
- }
3031
- updateCallback();
3032
- filesPicker.value = "";
3033
- if (instance && pathKey && !state.config.readonly) {
3034
- instance.triggerOnChange(pathKey, initialFiles);
3514
+ const dropzone = document.createElement("div");
3515
+ dropzone.className = "fb-file-dropzone";
3516
+ const subHint2 = buildSubHint();
3517
+ dropzone.innerHTML = `
3518
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;color:var(--fb-file-upload-text-color,#9ca3af);">
3519
+ <path d="M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
3520
+ </svg>
3521
+ <div class="fb-dropzone-primary-text">${escapeHtml(t("clickDragTextMultiple", state))}</div>
3522
+ ${subHint2 ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint2)}</div>` : ""}
3523
+ `;
3524
+ dropzone.onclick = openPicker;
3525
+ container.appendChild(dropzone);
3035
3526
  }
3036
- };
3527
+ return;
3528
+ }
3529
+ const tilesWrap = document.createElement("div");
3530
+ tilesWrap.className = "fb-tiles-wrap";
3531
+ tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
3532
+ for (const rid of ridList) {
3533
+ const meta = state.resourceIndex.get(rid);
3534
+ const tile = createFileTile();
3535
+ tile.classList.add("fb-tile-resource", "resource-pill");
3536
+ tile.dataset.resourceId = rid;
3537
+ const actionsEl = createTileActions({
3538
+ canRemove: !isReadonly && onRemove !== null,
3539
+ removeHandler: onRemove ? () => onRemove(rid) : null,
3540
+ state,
3541
+ resourceId: rid,
3542
+ fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : ""
3543
+ });
3544
+ fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
3545
+ console.error("Failed to render tile:", err);
3546
+ });
3547
+ tilesWrap.appendChild(tile);
3548
+ }
3549
+ if (!isReadonly && !atMax) {
3550
+ const addTile = document.createElement("div");
3551
+ addTile.className = "fb-tile fb-tile-add";
3552
+ addTile.innerHTML = "+";
3553
+ addTile.onclick = openPicker;
3554
+ tilesWrap.appendChild(addTile);
3555
+ } else if (!isReadonly && atMax) {
3556
+ const chip = document.createElement("div");
3557
+ chip.className = "fb-tile-counter";
3558
+ chip.textContent = t("filesCounter", state, {
3559
+ count: ridList.length,
3560
+ max: maxCount
3561
+ });
3562
+ tilesWrap.appendChild(chip);
3563
+ }
3564
+ container.appendChild(tilesWrap);
3565
+ const subHint = buildSubHint();
3566
+ if (subHint) {
3567
+ const hintEl = document.createElement("div");
3568
+ hintEl.className = "fb-tile-hint";
3569
+ hintEl.textContent = subHint;
3570
+ container.appendChild(hintEl);
3571
+ }
3037
3572
  }
3038
- function renderFileElement(element, ctx, wrapper, pathKey) {
3039
- var _a, _b;
3573
+ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3574
+ var _a, _b, _c;
3040
3575
  const state = ctx.state;
3041
- if (state.config.readonly) {
3042
- const initial = ctx.prefill[element.key];
3043
- if (initial) {
3044
- renderFilePreviewReadonly(initial, state).then((filePreview) => {
3045
- wrapper.appendChild(filePreview);
3046
- }).catch((err) => {
3047
- console.error("Failed to render file preview:", err);
3048
- const emptyState = document.createElement("div");
3049
- emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
3050
- emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("previewUnavailable", state))}</div>`;
3051
- wrapper.appendChild(emptyState);
3052
- });
3053
- } else {
3054
- const emptyState = document.createElement("div");
3055
- emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
3056
- emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("noFileSelected", state))}</div>`;
3057
- wrapper.appendChild(emptyState);
3058
- }
3059
- } else {
3060
- const fileWrapper = document.createElement("div");
3061
- fileWrapper.className = "space-y-2";
3062
- const picker = document.createElement("input");
3063
- picker.type = "file";
3064
- picker.name = pathKey;
3065
- picker.style.display = "none";
3066
- if (element.accept) {
3067
- picker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
3068
- }
3069
- const fileContainer = document.createElement("div");
3070
- fileContainer.className = "file-preview-container w-full aspect-square max-w-xs bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
3071
- const initial = ctx.prefill[element.key];
3072
- const allowedExts = getAllowedExtensions(element.accept);
3073
- const maxSizeMB = (_b = element.maxSize) != null ? _b : Infinity;
3074
- const fileUploadHandler = () => picker.click();
3075
- const dragHandler = (files) => {
3576
+ const fileWrapper = document.createElement("div");
3577
+ fileWrapper.className = "space-y-2";
3578
+ const picker = document.createElement("input");
3579
+ picker.type = "file";
3580
+ picker.name = pathKey;
3581
+ picker.style.display = "none";
3582
+ if (element.accept) {
3583
+ picker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
3584
+ }
3585
+ const fileContainer = document.createElement("div");
3586
+ fileContainer.className = "file-preview-container";
3587
+ const initial = ctx.prefill[element.key];
3588
+ const allowedExts = getAllowedExtensions(element.accept);
3589
+ const maxSizeMB = (_b = element.maxSize) != null ? _b : Infinity;
3590
+ const handlers = {
3591
+ fileUploadHandler() {
3592
+ picker.click();
3593
+ },
3594
+ dragHandler(files) {
3076
3595
  if (files.length > 0) {
3077
- const deps = { picker, fileUploadHandler, dragHandler };
3078
3596
  handleFileSelect(
3079
3597
  files[0],
3080
3598
  fileContainer,
3081
3599
  pathKey,
3082
3600
  state,
3083
- deps,
3601
+ buildDeps(),
3084
3602
  ctx.instance,
3085
3603
  allowedExts,
3086
3604
  maxSizeMB
3087
3605
  );
3088
3606
  }
3089
- };
3090
- if (initial) {
3091
- handleInitialFileData(
3092
- initial,
3607
+ },
3608
+ setupDrop(container) {
3609
+ setupDragAndDrop(container, handlers.dragHandler);
3610
+ },
3611
+ restoreDropzone() {
3612
+ const hint = makeFieldHint(element, state);
3613
+ fileContainer.className = "file-preview-container w-full max-w-md bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
3614
+ fileContainer.style.height = "128px";
3615
+ setEmptyFileContainer(fileContainer, state, hint);
3616
+ fileContainer.onclick = handlers.fileUploadHandler;
3617
+ setupDragAndDrop(fileContainer, handlers.dragHandler);
3618
+ },
3619
+ onRemove() {
3620
+ var _a2;
3621
+ const hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
3622
+ const currentRid = hiddenInput == null ? void 0 : hiddenInput.value;
3623
+ if (currentRid) {
3624
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
3625
+ }
3626
+ if (hiddenInput) hiddenInput.value = "";
3627
+ handlers.restoreDropzone();
3628
+ }
3629
+ };
3630
+ const buildDeps = () => ({
3631
+ picker,
3632
+ fileUploadHandler: handlers.fileUploadHandler,
3633
+ dragHandler: handlers.dragHandler,
3634
+ setupDrop: handlers.setupDrop,
3635
+ onRemove: handlers.onRemove
3636
+ });
3637
+ if (initial) {
3638
+ handleInitialFileData(
3639
+ initial,
3640
+ fileContainer,
3641
+ pathKey,
3642
+ fileWrapper,
3643
+ state,
3644
+ buildDeps()
3645
+ );
3646
+ const prefillMeta = state.resourceIndex.get(initial);
3647
+ if ((_c = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _c.startsWith("video/")) {
3648
+ fileContainer.onclick = handlers.fileUploadHandler;
3649
+ setupDragAndDrop(fileContainer, handlers.dragHandler);
3650
+ }
3651
+ } else {
3652
+ handlers.restoreDropzone();
3653
+ }
3654
+ picker.onchange = () => {
3655
+ if (picker.files && picker.files.length > 0) {
3656
+ handleFileSelect(
3657
+ picker.files[0],
3093
3658
  fileContainer,
3094
3659
  pathKey,
3095
- fileWrapper,
3096
3660
  state,
3097
- {
3098
- picker,
3099
- fileUploadHandler,
3100
- dragHandler
3101
- }
3661
+ buildDeps(),
3662
+ ctx.instance,
3663
+ allowedExts,
3664
+ maxSizeMB
3102
3665
  );
3103
- } else {
3104
- const hint = makeFieldHint(element, state);
3105
- setEmptyFileContainer(fileContainer, state, hint);
3106
3666
  }
3107
- fileContainer.onclick = fileUploadHandler;
3108
- setupDragAndDrop(fileContainer, dragHandler);
3109
- picker.onchange = () => {
3110
- if (picker.files && picker.files.length > 0) {
3111
- const deps = { picker, fileUploadHandler, dragHandler };
3112
- handleFileSelect(
3113
- picker.files[0],
3114
- fileContainer,
3115
- pathKey,
3116
- state,
3117
- deps,
3118
- ctx.instance,
3119
- allowedExts,
3120
- maxSizeMB
3121
- );
3122
- }
3123
- };
3124
- fileWrapper.appendChild(fileContainer);
3125
- fileWrapper.appendChild(picker);
3126
- wrapper.appendChild(fileWrapper);
3127
- }
3667
+ };
3668
+ fileWrapper.appendChild(fileContainer);
3669
+ fileWrapper.appendChild(picker);
3670
+ wrapper.appendChild(fileWrapper);
3128
3671
  }
3129
- function renderFilesElement(element, ctx, wrapper, pathKey) {
3672
+ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
3130
3673
  var _a, _b;
3131
3674
  const state = ctx.state;
3132
- if (state.config.readonly) {
3133
- const resultsWrapper = document.createElement("div");
3134
- resultsWrapper.className = "space-y-4";
3135
- const initialFiles = ctx.prefill[element.key] || [];
3136
- if (initialFiles.length > 0) {
3137
- initialFiles.forEach((resourceId) => {
3138
- renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
3139
- resultsWrapper.appendChild(filePreview);
3140
- }).catch((err) => {
3141
- console.error("Failed to render file preview:", err);
3142
- });
3143
- });
3144
- } else {
3145
- resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${escapeHtml(t("noFilesSelected", state))}</div></div>`;
3146
- }
3147
- wrapper.appendChild(resultsWrapper);
3148
- } else {
3149
- let updateFilesList2 = function() {
3150
- renderResourcePills(
3151
- list,
3152
- initialFiles,
3153
- state,
3154
- (ridToRemove) => {
3155
- const index = initialFiles.indexOf(ridToRemove);
3156
- if (index > -1) {
3157
- initialFiles.splice(index, 1);
3158
- }
3159
- updateFilesList2();
3160
- },
3161
- filesFieldHint
3162
- );
3163
- };
3164
- const filesWrapper = document.createElement("div");
3165
- filesWrapper.className = "space-y-2";
3166
- const filesPicker = document.createElement("input");
3167
- filesPicker.type = "file";
3168
- filesPicker.name = pathKey;
3169
- filesPicker.multiple = true;
3170
- filesPicker.style.display = "none";
3171
- if (element.accept) {
3172
- filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
3173
- }
3174
- const filesContainer = document.createElement("div");
3175
- filesContainer.className = "border-2 border-dashed border-gray-300 rounded-lg p-3 hover:border-gray-400 transition-colors";
3176
- const list = document.createElement("div");
3177
- list.className = "files-list";
3178
- const initialFiles = ctx.prefill[element.key] || [];
3179
- addPrefillFilesToIndex(initialFiles, state);
3180
- const filesFieldHint = makeFieldHint(element, state);
3181
- const filesConstraints = {
3182
- maxCount: Infinity,
3183
- allowedExtensions: getAllowedExtensions(element.accept),
3184
- maxSize: (_b = element.maxSize) != null ? _b : Infinity
3185
- };
3186
- updateFilesList2();
3187
- setupFilesDropHandler(
3188
- filesContainer,
3189
- initialFiles,
3190
- state,
3191
- updateFilesList2,
3192
- filesConstraints,
3193
- pathKey,
3194
- ctx.instance
3195
- );
3196
- setupFilesPickerHandler(
3197
- filesPicker,
3675
+ const filesWrapper = document.createElement("div");
3676
+ filesWrapper.className = "space-y-2";
3677
+ filesWrapper.dataset.filesWrapper = pathKey;
3678
+ const filesPicker = document.createElement("input");
3679
+ filesPicker.type = "file";
3680
+ filesPicker.name = pathKey;
3681
+ filesPicker.multiple = true;
3682
+ filesPicker.style.display = "none";
3683
+ if (element.accept) {
3684
+ filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
3685
+ }
3686
+ const filesContainer = document.createElement("div");
3687
+ filesContainer.className = "files-list-wrapper";
3688
+ filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
3689
+ const list = document.createElement("div");
3690
+ list.className = "files-list";
3691
+ const initialFiles = ctx.prefill[element.key] || [];
3692
+ addPrefillFilesToIndex(initialFiles, state.resourceIndex);
3693
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
3694
+ const filesFieldHint = makeFieldHint(element, state);
3695
+ const filesConstraints = {
3696
+ maxCount: Infinity,
3697
+ allowedExtensions: getAllowedExtensions(element.accept),
3698
+ maxSize: (_b = element.maxSize) != null ? _b : Infinity
3699
+ };
3700
+ filesContainer.appendChild(list);
3701
+ filesWrapper.appendChild(filesPicker);
3702
+ filesWrapper.appendChild(filesContainer);
3703
+ wrapper.appendChild(filesWrapper);
3704
+ function updateFilesList() {
3705
+ const currentlyReadonly = isElementReadonly(element, state);
3706
+ renderResourcePills(
3707
+ list,
3198
3708
  initialFiles,
3199
3709
  state,
3200
- updateFilesList2,
3201
- filesConstraints,
3202
- pathKey,
3203
- ctx.instance
3710
+ currentlyReadonly ? null : (ridToRemove) => {
3711
+ var _a2;
3712
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
3713
+ const index = initialFiles.indexOf(ridToRemove);
3714
+ if (index > -1) initialFiles.splice(index, 1);
3715
+ updateFilesList();
3716
+ },
3717
+ filesFieldHint,
3718
+ void 0,
3719
+ void 0,
3720
+ currentlyReadonly
3204
3721
  );
3205
- filesContainer.appendChild(list);
3206
- filesWrapper.appendChild(filesContainer);
3207
- filesWrapper.appendChild(filesPicker);
3208
- wrapper.appendChild(filesWrapper);
3209
3722
  }
3723
+ updateFilesList();
3724
+ setupFilesDropHandler(
3725
+ filesContainer,
3726
+ initialFiles,
3727
+ state,
3728
+ updateFilesList,
3729
+ filesConstraints,
3730
+ pathKey,
3731
+ ctx.instance
3732
+ );
3733
+ setupFilesPickerHandler(
3734
+ filesPicker,
3735
+ initialFiles,
3736
+ state,
3737
+ updateFilesList,
3738
+ filesConstraints,
3739
+ pathKey,
3740
+ ctx.instance
3741
+ );
3210
3742
  }
3211
- function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
3743
+ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
3212
3744
  var _a, _b, _c, _d;
3213
3745
  const state = ctx.state;
3214
3746
  const minFiles = (_a = element.minCount) != null ? _a : 0;
3215
3747
  const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
3216
- if (state.config.readonly) {
3217
- const resultsWrapper = document.createElement("div");
3218
- resultsWrapper.className = "space-y-4";
3219
- const initialFiles = ctx.prefill[element.key] || [];
3220
- if (initialFiles.length > 0) {
3221
- initialFiles.forEach((resourceId) => {
3222
- renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
3223
- resultsWrapper.appendChild(filePreview);
3224
- }).catch((err) => {
3225
- console.error("Failed to render file preview:", err);
3226
- });
3227
- });
3228
- } else {
3229
- resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${escapeHtml(t("noFilesSelected", state))}</div></div>`;
3230
- }
3231
- wrapper.appendChild(resultsWrapper);
3232
- } else {
3233
- const filesWrapper = document.createElement("div");
3234
- filesWrapper.className = "space-y-2";
3235
- const filesPicker = document.createElement("input");
3236
- filesPicker.type = "file";
3237
- filesPicker.name = pathKey;
3238
- filesPicker.multiple = true;
3239
- filesPicker.style.display = "none";
3240
- if (element.accept) {
3241
- filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`).join(",")) || "";
3242
- }
3243
- const filesContainer = document.createElement("div");
3244
- filesContainer.className = "files-list space-y-2";
3245
- filesWrapper.appendChild(filesPicker);
3246
- filesWrapper.appendChild(filesContainer);
3247
- const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
3248
- addPrefillFilesToIndex(initialFiles, state);
3249
- const multipleFilesHint = makeFieldHint(element, state);
3250
- const multipleConstraints = {
3251
- maxCount: maxFiles,
3252
- allowedExtensions: getAllowedExtensions(element.accept),
3253
- maxSize: (_d = element.maxSize) != null ? _d : Infinity
3254
- };
3255
- const buildCountInfo = () => {
3256
- const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
3257
- const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
3258
- return countText + minMaxText;
3259
- };
3260
- const updateFilesDisplay = () => {
3261
- renderResourcePills(
3262
- filesContainer,
3263
- initialFiles,
3264
- state,
3265
- (index) => {
3266
- initialFiles.splice(initialFiles.indexOf(index), 1);
3267
- updateFilesDisplay();
3268
- },
3269
- multipleFilesHint,
3270
- buildCountInfo()
3271
- );
3272
- };
3273
- setupFilesDropHandler(
3274
- filesContainer,
3748
+ const filesWrapper = document.createElement("div");
3749
+ filesWrapper.className = "space-y-2";
3750
+ filesWrapper.dataset.filesWrapper = pathKey;
3751
+ const filesPicker = document.createElement("input");
3752
+ filesPicker.type = "file";
3753
+ filesPicker.name = pathKey;
3754
+ filesPicker.multiple = true;
3755
+ filesPicker.style.display = "none";
3756
+ if (element.accept) {
3757
+ filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`).join(",")) || "";
3758
+ }
3759
+ const filesContainer = document.createElement("div");
3760
+ filesContainer.className = "files-list-wrapper";
3761
+ filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
3762
+ const list = document.createElement("div");
3763
+ list.className = "files-list";
3764
+ filesWrapper.appendChild(filesPicker);
3765
+ filesWrapper.appendChild(filesContainer);
3766
+ filesContainer.appendChild(list);
3767
+ const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
3768
+ addPrefillFilesToIndex(initialFiles, state.resourceIndex);
3769
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
3770
+ const multipleFilesHint = makeFieldHint(element, state);
3771
+ const multipleConstraints = {
3772
+ maxCount: maxFiles,
3773
+ allowedExtensions: getAllowedExtensions(element.accept),
3774
+ maxSize: (_d = element.maxSize) != null ? _d : Infinity
3775
+ };
3776
+ const buildCountInfo = () => {
3777
+ const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
3778
+ const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
3779
+ return countText + minMaxText;
3780
+ };
3781
+ const updateFilesDisplay = () => {
3782
+ const currentlyReadonly = isElementReadonly(element, state);
3783
+ renderResourcePills(
3784
+ list,
3275
3785
  initialFiles,
3276
3786
  state,
3277
- updateFilesDisplay,
3278
- multipleConstraints,
3279
- pathKey,
3280
- ctx.instance
3787
+ currentlyReadonly ? null : (index) => {
3788
+ var _a2;
3789
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
3790
+ initialFiles.splice(initialFiles.indexOf(index), 1);
3791
+ updateFilesDisplay();
3792
+ },
3793
+ multipleFilesHint,
3794
+ buildCountInfo(),
3795
+ maxFiles < Infinity ? maxFiles : void 0,
3796
+ currentlyReadonly
3281
3797
  );
3282
- setupFilesPickerHandler(
3283
- filesPicker,
3284
- initialFiles,
3285
- state,
3286
- updateFilesDisplay,
3287
- multipleConstraints,
3288
- pathKey,
3289
- ctx.instance
3798
+ };
3799
+ setupFilesDropHandler(
3800
+ filesContainer,
3801
+ initialFiles,
3802
+ state,
3803
+ updateFilesDisplay,
3804
+ multipleConstraints,
3805
+ pathKey,
3806
+ ctx.instance
3807
+ );
3808
+ setupFilesPickerHandler(
3809
+ filesPicker,
3810
+ initialFiles,
3811
+ state,
3812
+ updateFilesDisplay,
3813
+ multipleConstraints,
3814
+ pathKey,
3815
+ ctx.instance
3816
+ );
3817
+ updateFilesDisplay();
3818
+ wrapper.appendChild(filesWrapper);
3819
+ }
3820
+
3821
+ // src/components/file/validate.ts
3822
+ function readMultiFileResourceIds(scopeRoot, fullKey) {
3823
+ const wrapper = scopeRoot.querySelector(
3824
+ `[data-files-wrapper="${fullKey}"]`
3825
+ );
3826
+ if (!wrapper) return [];
3827
+ const encoded = wrapper.dataset.resourceIds;
3828
+ if (encoded === void 0) {
3829
+ throw new Error(
3830
+ `readMultiFileResourceIds: [data-files-wrapper="${fullKey}"] is missing data-resource-ids attribute. This is a render bug.`
3831
+ );
3832
+ }
3833
+ const parsed = JSON.parse(encoded);
3834
+ if (!Array.isArray(parsed)) {
3835
+ throw new Error(
3836
+ `readMultiFileResourceIds: data-resource-ids on [data-files-wrapper="${fullKey}"] is not a JSON array. Got: ${encoded}`
3290
3837
  );
3291
- updateFilesDisplay();
3292
- wrapper.appendChild(filesWrapper);
3293
3838
  }
3839
+ return parsed;
3294
3840
  }
3295
- function validateFileElement(element, key, context) {
3841
+ function validateFileCount(key, resourceIds, element, state, errors) {
3842
+ var _a, _b;
3843
+ const minFiles = "minCount" in element ? (_a = element.minCount) != null ? _a : 0 : 0;
3844
+ const maxFiles = "maxCount" in element ? (_b = element.maxCount) != null ? _b : Infinity : Infinity;
3845
+ if (element.required && resourceIds.length === 0) {
3846
+ errors.push(`${key}: ${t("required", state)}`);
3847
+ }
3848
+ if (resourceIds.length < minFiles) {
3849
+ errors.push(`${key}: ${t("minFiles", state, { min: minFiles })}`);
3850
+ }
3851
+ if (resourceIds.length > maxFiles) {
3852
+ errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
3853
+ }
3854
+ }
3855
+ function validateFileExtensions(key, resourceIds, element, state, errors) {
3296
3856
  var _a;
3297
- const errors = [];
3298
- const { scopeRoot, skipValidation, path } = context;
3299
- const isMultipleField = element.type === "files" || "multiple" in element && Boolean(element.multiple);
3300
- const validateFileCount = (key2, resourceIds, element2) => {
3301
- var _a2, _b;
3302
- if (skipValidation) return;
3303
- const { state } = context;
3304
- const minFiles = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 0 : 0;
3305
- const maxFiles = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
3306
- if (element2.required && resourceIds.length === 0) {
3307
- errors.push(`${key2}: ${t("required", state)}`);
3308
- }
3309
- if (resourceIds.length < minFiles) {
3310
- errors.push(`${key2}: ${t("minFiles", state, { min: minFiles })}`);
3311
- }
3312
- if (resourceIds.length > maxFiles) {
3313
- errors.push(`${key2}: ${t("maxFiles", state, { max: maxFiles })}`);
3314
- }
3315
- };
3316
- const validateFileExtensions = (key2, resourceIds, element2) => {
3317
- var _a2;
3318
- if (skipValidation) return;
3319
- const { state } = context;
3320
- const acceptField = "accept" in element2 ? element2.accept : void 0;
3321
- const allowedExtensions = getAllowedExtensions(acceptField);
3322
- if (allowedExtensions.length === 0) return;
3323
- const formats = allowedExtensions.join(", ");
3324
- for (const rid of resourceIds) {
3325
- const meta = state.resourceIndex.get(rid);
3326
- const fileName = (_a2 = meta == null ? void 0 : meta.name) != null ? _a2 : rid;
3327
- if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
3328
- errors.push(
3329
- `${key2}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
3330
- );
3331
- }
3857
+ const acceptField = "accept" in element ? element.accept : void 0;
3858
+ const allowedExtensions = getAllowedExtensions(acceptField);
3859
+ if (allowedExtensions.length === 0) return;
3860
+ const formats = allowedExtensions.join(", ");
3861
+ for (const rid of resourceIds) {
3862
+ const meta = state.resourceIndex.get(rid);
3863
+ const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
3864
+ if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
3865
+ errors.push(
3866
+ `${key}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
3867
+ );
3332
3868
  }
3333
- };
3334
- const validateFileSizes = (key2, resourceIds, element2) => {
3335
- var _a2;
3336
- if (skipValidation) return;
3337
- const { state } = context;
3338
- const maxSizeMB = "maxSize" in element2 ? (_a2 = element2.maxSize) != null ? _a2 : Infinity : Infinity;
3339
- if (maxSizeMB === Infinity) return;
3340
- for (const rid of resourceIds) {
3341
- const meta = state.resourceIndex.get(rid);
3342
- if (!meta) continue;
3343
- if (meta.size > maxSizeMB * 1024 * 1024) {
3344
- errors.push(
3345
- `${key2}: ${t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })}`
3346
- );
3347
- }
3869
+ }
3870
+ }
3871
+ function validateFileSizes(key, resourceIds, element, state, errors) {
3872
+ var _a;
3873
+ const maxSizeMB = "maxSize" in element ? (_a = element.maxSize) != null ? _a : Infinity : Infinity;
3874
+ if (maxSizeMB === Infinity) return;
3875
+ for (const rid of resourceIds) {
3876
+ const meta = state.resourceIndex.get(rid);
3877
+ if (!meta) continue;
3878
+ if (meta.size > maxSizeMB * 1024 * 1024) {
3879
+ errors.push(
3880
+ `${key}: ${t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })}`
3881
+ );
3348
3882
  }
3349
- };
3883
+ }
3884
+ }
3885
+ function validateMultiFile(element, key, context) {
3886
+ const { scopeRoot, skipValidation, path, state } = context;
3887
+ const errors = [];
3888
+ const fullKey = pathJoin(path, key);
3889
+ const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
3890
+ if (!skipValidation) {
3891
+ validateFileCount(key, resourceIds, element, state, errors);
3892
+ validateFileExtensions(key, resourceIds, element, state, errors);
3893
+ validateFileSizes(key, resourceIds, element, state, errors);
3894
+ }
3895
+ return { value: resourceIds, errors };
3896
+ }
3897
+ function validateSingleFile(element, key, context) {
3898
+ var _a;
3899
+ const { scopeRoot, skipValidation, state } = context;
3900
+ const errors = [];
3901
+ const input = scopeRoot.querySelector(
3902
+ `input[name$="${key}"][type="hidden"]`
3903
+ );
3904
+ const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
3905
+ if (!skipValidation && element.required && rid === "") {
3906
+ errors.push(`${key}: ${t("required", state)}`);
3907
+ return { value: null, errors };
3908
+ }
3909
+ if (!skipValidation && rid !== "") {
3910
+ validateFileExtensions(key, [rid], element, state, errors);
3911
+ validateFileSizes(key, [rid], element, state, errors);
3912
+ }
3913
+ return { value: rid || null, errors };
3914
+ }
3915
+ function validateFileElement(element, key, context) {
3916
+ const isMultipleField = element.type === "files" || "multiple" in element && Boolean(element.multiple);
3350
3917
  if (isMultipleField) {
3351
- const fullKey = pathJoin(path, key);
3352
- const pickerInput = scopeRoot.querySelector(
3353
- `input[type="file"][name="${fullKey}"]`
3354
- );
3355
- const filesWrapper = pickerInput == null ? void 0 : pickerInput.closest(".space-y-2");
3356
- const container = (filesWrapper == null ? void 0 : filesWrapper.querySelector(".files-list")) || null;
3357
- const resourceIds = [];
3358
- if (container) {
3359
- const pills = container.querySelectorAll(".resource-pill");
3360
- pills.forEach((pill) => {
3361
- const resourceId = pill.dataset.resourceId;
3362
- if (resourceId) {
3363
- resourceIds.push(resourceId);
3364
- }
3365
- });
3366
- }
3367
- validateFileCount(key, resourceIds, element);
3368
- validateFileExtensions(key, resourceIds, element);
3369
- validateFileSizes(key, resourceIds, element);
3370
- return { value: resourceIds, errors };
3918
+ return validateMultiFile(element, key, context);
3919
+ }
3920
+ return validateSingleFile(element, key, context);
3921
+ }
3922
+
3923
+ // src/components/file/render-readonly.ts
3924
+ function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
3925
+ const state = ctx.state;
3926
+ const rawInitial = ctx.prefill[element.key];
3927
+ const initial = typeof rawInitial === "string" ? rawInitial : "";
3928
+ if (initial) {
3929
+ addPrefillFilesToIndex([initial], state.resourceIndex);
3930
+ const hiddenInput = document.createElement("input");
3931
+ hiddenInput.type = "hidden";
3932
+ hiddenInput.name = pathKey;
3933
+ hiddenInput.value = initial;
3934
+ wrapper.appendChild(hiddenInput);
3935
+ renderFilePreviewReadonly(initial, state).then((filePreview) => {
3936
+ wrapper.appendChild(filePreview);
3937
+ }).catch((err) => {
3938
+ console.error("Failed to render file preview:", err);
3939
+ wrapper.appendChild(buildEmptyReadonlyTile(state));
3940
+ });
3371
3941
  } else {
3372
- const input = scopeRoot.querySelector(
3373
- `input[name$="${key}"][type="hidden"]`
3374
- );
3375
- const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
3376
- if (!skipValidation && element.required && rid === "") {
3377
- errors.push(`${key}: ${t("required", context.state)}`);
3378
- return { value: null, errors };
3379
- }
3380
- if (!skipValidation && rid !== "") {
3381
- validateFileExtensions(key, [rid], element);
3382
- validateFileSizes(key, [rid], element);
3383
- }
3384
- return { value: rid || null, errors };
3942
+ wrapper.appendChild(buildEmptyReadonlyTile(state));
3943
+ }
3944
+ }
3945
+ function buildEmptyReadonlyTile(state) {
3946
+ const emptyState = document.createElement("div");
3947
+ emptyState.style.cssText = `
3948
+ width:${TILE_SIZE};
3949
+ height:${TILE_SIZE};
3950
+ display:flex;
3951
+ align-items:center;
3952
+ justify-content:center;
3953
+ background:var(--fb-file-upload-bg-color,#f3f4f6);
3954
+ border-radius:var(--fb-border-radius,0.5rem);
3955
+ border:1px solid var(--fb-file-upload-border-color,#d1d5db);
3956
+ `;
3957
+ emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
3958
+ return emptyState;
3959
+ }
3960
+ function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
3961
+ addPrefillFilesToIndex(rids, state.resourceIndex);
3962
+ const filesWrapper = document.createElement("div");
3963
+ filesWrapper.dataset.filesWrapper = pathKey;
3964
+ filesWrapper.dataset.resourceIds = JSON.stringify(rids);
3965
+ wrapper.appendChild(filesWrapper);
3966
+ if (rids.length === 0) {
3967
+ const emptyEl = document.createElement("div");
3968
+ emptyEl.className = "fb-tile-empty-text";
3969
+ emptyEl.textContent = t("noFilesSelected", state);
3970
+ filesWrapper.appendChild(emptyEl);
3971
+ return;
3972
+ }
3973
+ const tilesWrap = document.createElement("div");
3974
+ tilesWrap.style.cssText = `display:flex;flex-wrap:wrap;gap:6px;${marginTop ? `margin-top:${marginTop};` : ""}`;
3975
+ filesWrapper.appendChild(tilesWrap);
3976
+ const placeholders = rids.map(() => {
3977
+ const placeholder = document.createElement("div");
3978
+ placeholder.style.cssText = `width:${TILE_SIZE};height:${TILE_SIZE};`;
3979
+ tilesWrap.appendChild(placeholder);
3980
+ return placeholder;
3981
+ });
3982
+ for (let i = 0; i < rids.length; i++) {
3983
+ const resourceId = rids[i];
3984
+ const placeholder = placeholders[i];
3985
+ renderFilePreviewReadonly(resourceId, state).then((tileEl) => {
3986
+ placeholder.replaceWith(tileEl);
3987
+ }).catch((err) => {
3988
+ console.error("Failed to render readonly tile:", err);
3989
+ });
3990
+ }
3991
+ }
3992
+ function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
3993
+ const rawPrefill = ctx.prefill[element.key];
3994
+ const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
3995
+ renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
3996
+ }
3997
+ function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
3998
+ const rawPrefill = ctx.prefill[element.key];
3999
+ const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
4000
+ renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey, "4px");
4001
+ }
4002
+
4003
+ // src/components/file.ts
4004
+ function renderFileElement(element, ctx, wrapper, pathKey) {
4005
+ if (isElementReadonly(element, ctx.state, ctx)) {
4006
+ renderFileElementReadonly(element, ctx, wrapper, pathKey);
4007
+ } else {
4008
+ renderFileElementEdit(element, ctx, wrapper, pathKey);
4009
+ }
4010
+ }
4011
+ function renderFilesElement(element, ctx, wrapper, pathKey) {
4012
+ if (isElementReadonly(element, ctx.state, ctx)) {
4013
+ renderFilesElementReadonly(element, ctx, wrapper, pathKey);
4014
+ } else {
4015
+ renderFilesElementEdit(element, ctx, wrapper, pathKey);
4016
+ }
4017
+ }
4018
+ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
4019
+ if (isElementReadonly(element, ctx.state, ctx)) {
4020
+ renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey);
4021
+ } else {
4022
+ renderMultipleFileElementEdit(element, ctx, wrapper, pathKey);
3385
4023
  }
3386
4024
  }
3387
4025
  function updateFileField(element, fieldPath, value, context) {
3388
- var _a;
3389
4026
  const { scopeRoot, state } = context;
3390
- if ("multiple" in element && element.multiple) {
4027
+ if (element.type === "files" || "multiple" in element && element.multiple) {
3391
4028
  if (!Array.isArray(value)) {
3392
4029
  console.warn(
3393
4030
  `updateFileField: Expected array for multiple file field "${fieldPath}", got ${typeof value}`
@@ -3395,32 +4032,22 @@ function updateFileField(element, fieldPath, value, context) {
3395
4032
  return;
3396
4033
  }
3397
4034
  value.forEach((resourceId) => {
3398
- var _a2;
3399
4035
  if (resourceId && typeof resourceId === "string") {
3400
4036
  if (!state.resourceIndex.has(resourceId)) {
3401
- const filename = resourceId.split("/").pop() || "file";
3402
- const extension = (_a2 = filename.split(".").pop()) == null ? void 0 : _a2.toLowerCase();
3403
- let fileType = "application/octet-stream";
3404
- if (extension) {
3405
- if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
3406
- fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
3407
- } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
3408
- fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
3409
- }
3410
- }
3411
- state.resourceIndex.set(resourceId, {
3412
- name: filename,
3413
- type: fileType,
3414
- size: 0,
3415
- uploadedAt: /* @__PURE__ */ new Date(),
3416
- file: void 0
3417
- });
4037
+ addResourceToIndex(resourceId, state);
3418
4038
  }
3419
4039
  }
3420
4040
  });
3421
- console.info(
3422
- `updateFileField: Multiple file field "${fieldPath}" updated. Preview update requires re-render.`
4041
+ const filesWrapper = scopeRoot.querySelector(
4042
+ `[data-files-wrapper="${fieldPath}"]`
3423
4043
  );
4044
+ if (filesWrapper) {
4045
+ filesWrapper.dataset.resourceIds = JSON.stringify(value);
4046
+ } else {
4047
+ console.warn(
4048
+ `updateFileField: [data-files-wrapper="${fieldPath}"] not found in DOM; data-resource-ids not updated`
4049
+ );
4050
+ }
3424
4051
  } else {
3425
4052
  const hiddenInput = scopeRoot.querySelector(
3426
4053
  `input[name="${fieldPath}"][type="hidden"]`
@@ -3434,23 +4061,7 @@ function updateFileField(element, fieldPath, value, context) {
3434
4061
  hiddenInput.value = value != null ? String(value) : "";
3435
4062
  if (value && typeof value === "string") {
3436
4063
  if (!state.resourceIndex.has(value)) {
3437
- const filename = value.split("/").pop() || "file";
3438
- const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
3439
- let fileType = "application/octet-stream";
3440
- if (extension) {
3441
- if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
3442
- fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
3443
- } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
3444
- fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
3445
- }
3446
- }
3447
- state.resourceIndex.set(value, {
3448
- name: filename,
3449
- type: fileType,
3450
- size: 0,
3451
- uploadedAt: /* @__PURE__ */ new Date(),
3452
- file: void 0
3453
- });
4064
+ addResourceToIndex(value, state);
3454
4065
  }
3455
4066
  console.info(
3456
4067
  `updateFileField: File field "${fieldPath}" updated. Preview update requires re-render.`
@@ -3458,6 +4069,26 @@ function updateFileField(element, fieldPath, value, context) {
3458
4069
  }
3459
4070
  }
3460
4071
  }
4072
+ function addResourceToIndex(resourceId, state) {
4073
+ var _a;
4074
+ const filename = resourceId.split("/").pop() || "file";
4075
+ const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
4076
+ let fileType = "application/octet-stream";
4077
+ if (extension) {
4078
+ if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
4079
+ fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
4080
+ } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
4081
+ fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
4082
+ }
4083
+ }
4084
+ state.resourceIndex.set(resourceId, {
4085
+ name: filename,
4086
+ type: fileType,
4087
+ size: 0,
4088
+ uploadedAt: /* @__PURE__ */ new Date(),
4089
+ file: void 0
4090
+ });
4091
+ }
3461
4092
 
3462
4093
  // src/components/colour.ts
3463
4094
  function normalizeColourValue(value) {
@@ -3613,15 +4244,16 @@ function createEditColourUI(value, pathKey, ctx) {
3613
4244
  }
3614
4245
  function renderColourElement(element, ctx, wrapper, pathKey) {
3615
4246
  const state = ctx.state;
4247
+ const readonly = isElementReadonly(element, state, ctx);
3616
4248
  const initialValue = ctx.prefill[element.key] || element.default || "#000000";
3617
- if (state.config.readonly) {
4249
+ if (readonly) {
3618
4250
  const readonlyUI = createReadonlyColourUI(initialValue);
3619
4251
  wrapper.appendChild(readonlyUI);
3620
4252
  } else {
3621
4253
  const editUI = createEditColourUI(initialValue, pathKey, ctx);
3622
4254
  wrapper.appendChild(editUI);
3623
4255
  }
3624
- if (!state.config.readonly) {
4256
+ if (!readonly) {
3625
4257
  const colourHint = document.createElement("p");
3626
4258
  colourHint.className = "mt-1";
3627
4259
  colourHint.style.cssText = `
@@ -3635,6 +4267,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
3635
4267
  function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3636
4268
  var _a, _b;
3637
4269
  const state = ctx.state;
4270
+ const readonly = isElementReadonly(element, state, ctx);
3638
4271
  const prefillValues = ctx.prefill[element.key] || [];
3639
4272
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
3640
4273
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -3657,7 +4290,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3657
4290
  function addColourItem(value = "#000000", index = -1) {
3658
4291
  const itemWrapper = document.createElement("div");
3659
4292
  itemWrapper.className = "multiple-colour-item flex items-center gap-2";
3660
- if (state.config.readonly) {
4293
+ if (readonly) {
3661
4294
  const readonlyUI = createReadonlyColourUI(value);
3662
4295
  while (readonlyUI.firstChild) {
3663
4296
  itemWrapper.appendChild(readonlyUI.firstChild);
@@ -3677,7 +4310,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3677
4310
  return itemWrapper;
3678
4311
  }
3679
4312
  function updateRemoveButtons() {
3680
- if (state.config.readonly) return;
4313
+ if (readonly) return;
3681
4314
  const items = container.querySelectorAll(".multiple-colour-item");
3682
4315
  const currentCount = items.length;
3683
4316
  items.forEach((item) => {
@@ -3722,7 +4355,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3722
4355
  }
3723
4356
  let addRow = null;
3724
4357
  let countDisplay = null;
3725
- if (!state.config.readonly) {
4358
+ if (!readonly) {
3726
4359
  addRow = document.createElement("div");
3727
4360
  addRow.className = "flex items-center gap-3 mt-2";
3728
4361
  const addBtn = document.createElement("button");
@@ -3769,7 +4402,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
3769
4402
  values.forEach((value) => addColourItem(value));
3770
4403
  updateAddButton();
3771
4404
  updateRemoveButtons();
3772
- if (!state.config.readonly) {
4405
+ if (!readonly) {
3773
4406
  const hint = document.createElement("p");
3774
4407
  hint.className = "mt-1";
3775
4408
  hint.style.cssText = `
@@ -3840,7 +4473,9 @@ function validateColourElement(element, key, context) {
3840
4473
  return normalized;
3841
4474
  };
3842
4475
  if (element.multiple) {
3843
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${key}["].colour-hex-input`);
4476
+ const hexInputs = scopeRoot.querySelectorAll(
4477
+ `[name^="${key}["].colour-hex-input`
4478
+ );
3844
4479
  const values = [];
3845
4480
  hexInputs.forEach((input, index) => {
3846
4481
  var _a2;
@@ -3888,7 +4523,9 @@ function updateColourField(element, fieldPath, value, context) {
3888
4523
  );
3889
4524
  return;
3890
4525
  }
3891
- const hexInputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["].colour-hex-input`);
4526
+ const hexInputs = scopeRoot.querySelectorAll(
4527
+ `[name^="${fieldPath}["].colour-hex-input`
4528
+ );
3892
4529
  hexInputs.forEach((hexInput, index) => {
3893
4530
  if (index < value.length) {
3894
4531
  const normalized = normalizeColourValue(value[index]);
@@ -4085,6 +4722,7 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4085
4722
  );
4086
4723
  }
4087
4724
  const state = ctx.state;
4725
+ const readonly = isElementReadonly(element, state, ctx);
4088
4726
  const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
4089
4727
  const initialValue = (_a = ctx.prefill[element.key]) != null ? _a : defaultValue;
4090
4728
  const sliderUI = createSliderUI(
@@ -4092,10 +4730,10 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
4092
4730
  pathKey,
4093
4731
  element,
4094
4732
  ctx,
4095
- state.config.readonly
4733
+ readonly
4096
4734
  );
4097
4735
  wrapper.appendChild(sliderUI);
4098
- if (!state.config.readonly) {
4736
+ if (!readonly) {
4099
4737
  const hint = document.createElement("p");
4100
4738
  hint.className = "mt-1";
4101
4739
  hint.style.cssText = `
@@ -4120,6 +4758,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4120
4758
  );
4121
4759
  }
4122
4760
  const state = ctx.state;
4761
+ const readonly = isElementReadonly(element, state, ctx);
4123
4762
  const prefillValues = ctx.prefill[element.key] || [];
4124
4763
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
4125
4764
  const minCount = (_a = element.minCount) != null ? _a : 1;
@@ -4144,13 +4783,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4144
4783
  const itemWrapper = document.createElement("div");
4145
4784
  itemWrapper.className = "multiple-slider-item flex items-start gap-2";
4146
4785
  const tempPathKey = `${pathKey}[${container.children.length}]`;
4147
- const sliderUI = createSliderUI(
4148
- value,
4149
- tempPathKey,
4150
- element,
4151
- ctx,
4152
- state.config.readonly
4153
- );
4786
+ const sliderUI = createSliderUI(value, tempPathKey, element, ctx, readonly);
4154
4787
  sliderUI.style.flex = "1";
4155
4788
  itemWrapper.appendChild(sliderUI);
4156
4789
  if (index === -1) {
@@ -4162,7 +4795,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4162
4795
  return itemWrapper;
4163
4796
  }
4164
4797
  function updateRemoveButtons() {
4165
- if (state.config.readonly) return;
4798
+ if (readonly) return;
4166
4799
  const items = container.querySelectorAll(".multiple-slider-item");
4167
4800
  const currentCount = items.length;
4168
4801
  items.forEach((item) => {
@@ -4208,7 +4841,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4208
4841
  }
4209
4842
  let addRow = null;
4210
4843
  let countDisplay = null;
4211
- if (!state.config.readonly) {
4844
+ if (!readonly) {
4212
4845
  addRow = document.createElement("div");
4213
4846
  addRow.className = "flex items-center gap-3 mt-2";
4214
4847
  const addBtn = document.createElement("button");
@@ -4254,7 +4887,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
4254
4887
  values.forEach((value) => addSliderItem(value));
4255
4888
  updateAddButton();
4256
4889
  updateRemoveButtons();
4257
- if (!state.config.readonly) {
4890
+ if (!readonly) {
4258
4891
  const hint = document.createElement("p");
4259
4892
  hint.className = "mt-1";
4260
4893
  hint.style.cssText = `
@@ -4496,9 +5129,7 @@ function mergeWithDefaults(prefill, defaults) {
4496
5129
  }
4497
5130
  function extractRootFormData(formRoot) {
4498
5131
  const data = {};
4499
- const inputs = formRoot.querySelectorAll(
4500
- "input, select, textarea"
4501
- );
5132
+ const inputs = formRoot.querySelectorAll("input, select, textarea");
4502
5133
  inputs.forEach((input) => {
4503
5134
  const fieldName = input.getAttribute("name");
4504
5135
  if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
@@ -4563,7 +5194,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4563
5194
  } else {
4564
5195
  itemsWrap.className = `grid grid-cols-${columns} gap-4`;
4565
5196
  }
4566
- if (!ctx.state.config.readonly) {
5197
+ const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
5198
+ if (!containerIsReadonly) {
4567
5199
  const hintsElement = createPrefillHints(element, pathKey);
4568
5200
  if (hintsElement) {
4569
5201
  containerWrap.appendChild(hintsElement);
@@ -4578,13 +5210,16 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4578
5210
  // Merged prefill with defaults for enableIf evaluation
4579
5211
  formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
4580
5212
  // Complete root data for enableIf evaluation
4581
- state: ctx.state
5213
+ state: ctx.state,
5214
+ inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
4582
5215
  };
4583
5216
  element.elements.forEach((child) => {
4584
5217
  var _a2, _b2;
4585
5218
  if (child.hidden || child.type === "hidden") {
4586
5219
  const prefillVal = (_b2 = (_a2 = containerPrefill[child.key]) != null ? _a2 : child.default) != null ? _b2 : null;
4587
- itemsWrap.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
5220
+ itemsWrap.appendChild(
5221
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
5222
+ );
4588
5223
  } else {
4589
5224
  itemsWrap.appendChild(renderElement(child, subCtx));
4590
5225
  }
@@ -4595,13 +5230,15 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
4595
5230
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4596
5231
  var _a, _b, _c, _d;
4597
5232
  const state = ctx.state;
5233
+ const containerIsReadonly = isElementReadonly(element, state, ctx);
5234
+ const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
4598
5235
  const containerWrap = document.createElement("div");
4599
5236
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
4600
5237
  const countDisplay = document.createElement("span");
4601
5238
  countDisplay.className = "text-sm text-gray-500";
4602
5239
  const itemsWrap = document.createElement("div");
4603
5240
  itemsWrap.className = "space-y-4";
4604
- if (!ctx.state.config.readonly) {
5241
+ if (!containerIsReadonly) {
4605
5242
  const hintsElement = createPrefillHints(element, element.key);
4606
5243
  if (hintsElement) {
4607
5244
  containerWrap.appendChild(hintsElement);
@@ -4639,8 +5276,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4639
5276
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4640
5277
  prefill: childDefaults,
4641
5278
  // Defaults for enableIf evaluation
4642
- formData: currentFormData
5279
+ formData: currentFormData,
4643
5280
  // Current root data from DOM for enableIf
5281
+ inheritedReadonly: childInheritedReadonly
4644
5282
  };
4645
5283
  const item = document.createElement("div");
4646
5284
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4655,13 +5293,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4655
5293
  element.elements.forEach((child) => {
4656
5294
  var _a2;
4657
5295
  if (child.hidden || child.type === "hidden") {
4658
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), (_a2 = child.default) != null ? _a2 : null));
5296
+ childWrapper.appendChild(
5297
+ createHiddenInput(
5298
+ pathJoin(subCtx.path, child.key),
5299
+ (_a2 = child.default) != null ? _a2 : null
5300
+ )
5301
+ );
4659
5302
  } else {
4660
5303
  childWrapper.appendChild(renderElement(child, subCtx));
4661
5304
  }
4662
5305
  });
4663
5306
  item.appendChild(childWrapper);
4664
- if (!state.config.readonly) {
5307
+ if (!containerIsReadonly) {
4665
5308
  const rem = document.createElement("button");
4666
5309
  rem.type = "button";
4667
5310
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4712,8 +5355,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4712
5355
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4713
5356
  prefill: mergedPrefill,
4714
5357
  // Merged prefill with defaults for enableIf
4715
- formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
5358
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill,
4716
5359
  // Complete root data for enableIf
5360
+ inheritedReadonly: childInheritedReadonly
4717
5361
  };
4718
5362
  const item = document.createElement("div");
4719
5363
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4729,13 +5373,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4729
5373
  var _a3, _b2;
4730
5374
  if (child.hidden || child.type === "hidden") {
4731
5375
  const prefillVal = (_b2 = (_a3 = prefillObj == null ? void 0 : prefillObj[child.key]) != null ? _a3 : child.default) != null ? _b2 : null;
4732
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
5376
+ childWrapper.appendChild(
5377
+ createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
5378
+ );
4733
5379
  } else {
4734
5380
  childWrapper.appendChild(renderElement(child, subCtx));
4735
5381
  }
4736
5382
  });
4737
5383
  item.appendChild(childWrapper);
4738
- if (!state.config.readonly) {
5384
+ if (!containerIsReadonly) {
4739
5385
  const rem = document.createElement("button");
4740
5386
  rem.type = "button";
4741
5387
  rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
@@ -4758,7 +5404,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4758
5404
  itemsWrap.appendChild(item);
4759
5405
  });
4760
5406
  }
4761
- if (!state.config.readonly) {
5407
+ if (!containerIsReadonly) {
4762
5408
  while (countItems() < min) {
4763
5409
  const idx = countItems();
4764
5410
  const subCtx = {
@@ -4766,8 +5412,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4766
5412
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
4767
5413
  prefill: childDefaults,
4768
5414
  // Defaults for enableIf evaluation
4769
- formData: (_d = ctx.formData) != null ? _d : ctx.prefill
5415
+ formData: (_d = ctx.formData) != null ? _d : ctx.prefill,
4770
5416
  // Complete root data for enableIf
5417
+ inheritedReadonly: childInheritedReadonly
4771
5418
  };
4772
5419
  const item = document.createElement("div");
4773
5420
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -4782,7 +5429,12 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4782
5429
  element.elements.forEach((child) => {
4783
5430
  var _a2;
4784
5431
  if (child.hidden || child.type === "hidden") {
4785
- childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), (_a2 = child.default) != null ? _a2 : null));
5432
+ childWrapper.appendChild(
5433
+ createHiddenInput(
5434
+ pathJoin(subCtx.path, child.key),
5435
+ (_a2 = child.default) != null ? _a2 : null
5436
+ )
5437
+ );
4786
5438
  } else {
4787
5439
  childWrapper.appendChild(renderElement(child, subCtx));
4788
5440
  }
@@ -4814,7 +5466,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
4814
5466
  }
4815
5467
  }
4816
5468
  containerWrap.appendChild(itemsWrap);
4817
- if (!state.config.readonly) {
5469
+ if (!containerIsReadonly) {
4818
5470
  const addRow = document.createElement("div");
4819
5471
  addRow.className = "flex items-center gap-3 mt-2";
4820
5472
  addRow.appendChild(createAddButton());
@@ -4977,8 +5629,10 @@ function updateContainerField(element, fieldPath, value, context) {
4977
5629
  const filesKey = (_b = richChild.filesKey) != null ? _b : "files";
4978
5630
  const containerValue = itemValue;
4979
5631
  const compositeValue = {};
4980
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
4981
- if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
5632
+ if (textKey in containerValue)
5633
+ compositeValue[textKey] = containerValue[textKey];
5634
+ if (filesKey in containerValue)
5635
+ compositeValue[filesKey] = containerValue[filesKey];
4982
5636
  if (Object.keys(compositeValue).length > 0) {
4983
5637
  instance.updateField(childPath, compositeValue);
4984
5638
  }
@@ -5015,8 +5669,10 @@ function updateContainerField(element, fieldPath, value, context) {
5015
5669
  const filesKey = (_b = richChild.filesKey) != null ? _b : "files";
5016
5670
  const containerValue = value;
5017
5671
  const compositeValue = {};
5018
- if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
5019
- if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
5672
+ if (textKey in containerValue)
5673
+ compositeValue[textKey] = containerValue[textKey];
5674
+ if (filesKey in containerValue)
5675
+ compositeValue[filesKey] = containerValue[filesKey];
5020
5676
  if (Object.keys(compositeValue).length > 0) {
5021
5677
  instance.updateField(childPath, compositeValue);
5022
5678
  }
@@ -5362,12 +6018,21 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5362
6018
  const hiddenInput = document.createElement("input");
5363
6019
  hiddenInput.type = "hidden";
5364
6020
  hiddenInput.name = pathKey;
5365
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
6021
+ hiddenInput.value = JSON.stringify({
6022
+ [cellsKey]: cells,
6023
+ [mergesKey]: merges
6024
+ });
5366
6025
  wrapper.appendChild(hiddenInput);
5367
6026
  function persistValue() {
5368
- hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
6027
+ hiddenInput.value = JSON.stringify({
6028
+ [cellsKey]: cells,
6029
+ [mergesKey]: merges
6030
+ });
5369
6031
  if (instance) {
5370
- instance.triggerOnChange(pathKey, { [cellsKey]: cells, [mergesKey]: merges });
6032
+ instance.triggerOnChange(pathKey, {
6033
+ [cellsKey]: cells,
6034
+ [mergesKey]: merges
6035
+ });
5371
6036
  }
5372
6037
  }
5373
6038
  hiddenInput._applyExternalUpdate = (data) => {
@@ -5427,9 +6092,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5427
6092
  rebuild();
5428
6093
  } catch (e) {
5429
6094
  const errMsg = e instanceof Error ? e.message : String(e);
5430
- console.error(
5431
- t("tableImportError", state).replace("{error}", errMsg)
5432
- );
6095
+ console.error(t("tableImportError", state).replace("{error}", errMsg));
5433
6096
  } finally {
5434
6097
  overlay.remove();
5435
6098
  }
@@ -5579,15 +6242,19 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5579
6242
  contextMenu.style.display = "none";
5580
6243
  }
5581
6244
  const menuDismissCtrl = new AbortController();
5582
- document.addEventListener("mousedown", (e) => {
5583
- if (!wrapper.isConnected) {
5584
- menuDismissCtrl.abort();
5585
- return;
5586
- }
5587
- if (!contextMenu.contains(e.target)) {
5588
- hideContextMenu();
5589
- }
5590
- }, { signal: menuDismissCtrl.signal });
6245
+ document.addEventListener(
6246
+ "mousedown",
6247
+ (e) => {
6248
+ if (!wrapper.isConnected) {
6249
+ menuDismissCtrl.abort();
6250
+ return;
6251
+ }
6252
+ if (!contextMenu.contains(e.target)) {
6253
+ hideContextMenu();
6254
+ }
6255
+ },
6256
+ { signal: menuDismissCtrl.signal }
6257
+ );
5591
6258
  function applySelectionStyles() {
5592
6259
  const range = selectionRange(sel);
5593
6260
  const allTds = tableEl.querySelectorAll("td[data-row]");
@@ -5940,7 +6607,9 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
5940
6607
  if (!anchor) return;
5941
6608
  const text = (_b3 = (_a3 = e.clipboardData) == null ? void 0 : _a3.getData("text/plain")) != null ? _b3 : "";
5942
6609
  const isMultiCell = text.includes(" ") || text.split(/\r?\n/).filter((l) => l).length > 1;
5943
- const editing = tableEl.querySelector("[contenteditable='true']");
6610
+ const editing = tableEl.querySelector(
6611
+ "[contenteditable='true']"
6612
+ );
5944
6613
  if (editing && !isMultiCell) return;
5945
6614
  e.preventDefault();
5946
6615
  if (editing) {
@@ -6100,7 +6769,11 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6100
6769
  }
6101
6770
  function updateTopZoneOverlays(mx, wr, tblR, scrollL, active) {
6102
6771
  var _a3;
6103
- const headerCells = active ? Array.from(tableEl.querySelectorAll("thead td[data-col]")) : [];
6772
+ const headerCells = active ? Array.from(
6773
+ tableEl.querySelectorAll(
6774
+ "thead td[data-col]"
6775
+ )
6776
+ ) : [];
6104
6777
  let closestColIdx = -1;
6105
6778
  let closestColDist = Infinity;
6106
6779
  let closestBorderX = -1;
@@ -6118,7 +6791,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6118
6791
  if (borderDist < closestBorderDist && borderDist < 20) {
6119
6792
  closestBorderDist = borderDist;
6120
6793
  closestBorderX = cellRect.right - wr.left + scrollL;
6121
- closestAfterCol = parseInt((_a3 = headerCells[i].getAttribute("data-col")) != null ? _a3 : "0", 10);
6794
+ closestAfterCol = parseInt(
6795
+ (_a3 = headerCells[i].getAttribute("data-col")) != null ? _a3 : "0",
6796
+ 10
6797
+ );
6122
6798
  }
6123
6799
  }
6124
6800
  colRemoveBtns.forEach((btn, idx) => {
@@ -6174,7 +6850,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6174
6850
  closestRowBorderDist = borderDist;
6175
6851
  closestBorderY = trRect.bottom - wr.top;
6176
6852
  const firstTd = allRowEls[i].querySelector("td[data-row]");
6177
- closestAfterRow = parseInt((_a3 = firstTd == null ? void 0 : firstTd.getAttribute("data-row")) != null ? _a3 : "0", 10);
6853
+ closestAfterRow = parseInt(
6854
+ (_a3 = firstTd == null ? void 0 : firstTd.getAttribute("data-row")) != null ? _a3 : "0",
6855
+ 10
6856
+ );
6178
6857
  }
6179
6858
  }
6180
6859
  rowRemoveBtns.forEach((btn, idx) => {
@@ -6200,7 +6879,8 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
6200
6879
  let rafPending = false;
6201
6880
  tableWrapper.onmousemove = (e) => {
6202
6881
  const target = e.target;
6203
- if (target.tagName === "BUTTON" && target.parentElement === tableWrapper) return;
6882
+ if (target.tagName === "BUTTON" && target.parentElement === tableWrapper)
6883
+ return;
6204
6884
  if (rafPending) return;
6205
6885
  rafPending = true;
6206
6886
  const mx = e.clientX;
@@ -6261,6 +6941,7 @@ function isTableDataWithFieldNames(v, cellsKey) {
6261
6941
  function renderTableElement(element, ctx, wrapper, pathKey) {
6262
6942
  var _a, _b, _c, _d;
6263
6943
  const state = ctx.state;
6944
+ const readonly = isElementReadonly(element, state, ctx);
6264
6945
  const rawPrefill = ctx.prefill[element.key];
6265
6946
  const cellsKey = (_b = (_a = element.fieldNames) == null ? void 0 : _a.cells) != null ? _b : "cells";
6266
6947
  const mergesKey = (_d = (_c = element.fieldNames) == null ? void 0 : _c.merges) != null ? _d : "merges";
@@ -6295,11 +6976,13 @@ function renderTableElement(element, ctx, wrapper, pathKey) {
6295
6976
  const cols = rows > 0 ? initialData.cells[0].length : 0;
6296
6977
  const err = validateMerges(initialData.merges, rows, cols);
6297
6978
  if (err) {
6298
- console.warn(`Table "${element.key}": invalid prefill merges stripped (${err})`);
6979
+ console.warn(
6980
+ `Table "${element.key}": invalid prefill merges stripped (${err})`
6981
+ );
6299
6982
  initialData = { ...initialData, merges: [] };
6300
6983
  }
6301
6984
  }
6302
- if (state.config.readonly) {
6985
+ if (readonly) {
6303
6986
  renderReadonlyTable(initialData, wrapper);
6304
6987
  } else {
6305
6988
  renderEditTable(element, initialData, pathKey, ctx, wrapper);
@@ -6995,14 +7678,20 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
6995
7678
  let mentionTooltip = null;
6996
7679
  backdrop.addEventListener("mouseover", (e) => {
6997
7680
  var _a2, _b;
6998
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(_a2, "mark");
7681
+ const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
7682
+ _a2,
7683
+ "mark"
7684
+ );
6999
7685
  if (!(mark == null ? void 0 : mark.dataset.rid)) return;
7000
7686
  mentionTooltip = removePortalTooltip(mentionTooltip);
7001
7687
  mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
7002
7688
  });
7003
7689
  backdrop.addEventListener("mouseout", (e) => {
7004
7690
  var _a2, _b, _c;
7005
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(_a2, "mark");
7691
+ const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
7692
+ _a2,
7693
+ "mark"
7694
+ );
7006
7695
  if (!mark) return;
7007
7696
  const related = e.relatedTarget;
7008
7697
  if ((_c = related == null ? void 0 : related.closest) == null ? void 0 : _c.call(related, "mark")) return;
@@ -7010,7 +7699,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7010
7699
  });
7011
7700
  backdrop.addEventListener("mousedown", (e) => {
7012
7701
  var _a2, _b;
7013
- const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(_a2, "mark");
7702
+ const mark = (_b = (_a2 = e.target).closest) == null ? void 0 : _b.call(
7703
+ _a2,
7704
+ "mark"
7705
+ );
7014
7706
  if (!mark) return;
7015
7707
  mentionTooltip = removePortalTooltip(mentionTooltip);
7016
7708
  const marks = backdrop.querySelectorAll("mark");
@@ -7263,11 +7955,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7263
7955
  textarea.addEventListener("keydown", (e) => {
7264
7956
  if (!dropdownState.open) return;
7265
7957
  const labels = buildFileLabelsFromClosure();
7266
- const filtered = filterFilesForDropdown(
7267
- dropdownState.query,
7268
- files,
7269
- labels
7270
- );
7958
+ const filtered = filterFilesForDropdown(dropdownState.query, files, labels);
7271
7959
  if (e.key === "ArrowDown") {
7272
7960
  e.preventDefault();
7273
7961
  dropdownState.selectedIndex = Math.min(
@@ -7602,6 +8290,7 @@ function renderReadonlyMode(_element, ctx, wrapper, _pathKey, value) {
7602
8290
  function renderRichInputElement(element, ctx, wrapper, pathKey) {
7603
8291
  var _a, _b, _c, _d;
7604
8292
  const state = ctx.state;
8293
+ const readonly = isElementReadonly(element, state, ctx);
7605
8294
  const textKey = (_a = element.textKey) != null ? _a : "text";
7606
8295
  const filesKey = (_b = element.filesKey) != null ? _b : "files";
7607
8296
  let initialValue;
@@ -7639,7 +8328,7 @@ function renderRichInputElement(element, ctx, wrapper, pathKey) {
7639
8328
  });
7640
8329
  }
7641
8330
  }
7642
- if (state.config.readonly) {
8331
+ if (readonly) {
7643
8332
  renderReadonlyMode(element, ctx, wrapper, pathKey, initialValue);
7644
8333
  } else {
7645
8334
  if (!state.config.uploadFile) {
@@ -7702,9 +8391,7 @@ function validateRichInputElement(element, key, context) {
7702
8391
  }
7703
8392
  }
7704
8393
  if (element.maxFiles != null && files.length > element.maxFiles) {
7705
- errors.push(
7706
- `${key}: ${t("maxFiles", state, { max: element.maxFiles })}`
7707
- );
8394
+ errors.push(`${key}: ${t("maxFiles", state, { max: element.maxFiles })}`);
7708
8395
  }
7709
8396
  }
7710
8397
  return { value, errors, spread: !!element.flatOutput };
@@ -7822,9 +8509,7 @@ function shouldDisableElement(element, ctx) {
7822
8509
  return false;
7823
8510
  }
7824
8511
  function extractDOMValue(fieldPath, formRoot) {
7825
- const input = formRoot.querySelector(
7826
- `[name="${fieldPath}"]`
7827
- );
8512
+ const input = formRoot.querySelector(`[name="${fieldPath}"]`);
7828
8513
  if (!input) {
7829
8514
  return void 0;
7830
8515
  }
@@ -7870,9 +8555,7 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7870
8555
  `[data-container-item="${containerKey}[${containerIndex}]"]`
7871
8556
  );
7872
8557
  if (containerItemElement) {
7873
- const inputs = containerItemElement.querySelectorAll(
7874
- "input, select, textarea"
7875
- );
8558
+ const inputs = containerItemElement.querySelectorAll("input, select, textarea");
7876
8559
  inputs.forEach((input) => {
7877
8560
  const fieldName = input.getAttribute("name");
7878
8561
  if (fieldName) {
@@ -7924,7 +8607,10 @@ function reevaluateEnableIf(wrapper, element, ctx) {
7924
8607
  wrapper.setAttribute("data-conditionally-disabled", "true");
7925
8608
  }
7926
8609
  } catch (error) {
7927
- console.error(`Error re-evaluating enableIf for field "${element.key}":`, error);
8610
+ console.error(
8611
+ `Error re-evaluating enableIf for field "${element.key}":`,
8612
+ error
8613
+ );
7928
8614
  }
7929
8615
  }
7930
8616
  function setupEnableIfListeners(wrapper, element, ctx) {
@@ -7946,14 +8632,10 @@ function setupEnableIfListeners(wrapper, element, ctx) {
7946
8632
  } else {
7947
8633
  dependencyFieldPath = dependencyKey;
7948
8634
  }
7949
- const dependencyInput = formRoot.querySelector(
7950
- `[name="${dependencyFieldPath}"]`
7951
- );
8635
+ const dependencyInput = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7952
8636
  if (!dependencyInput) {
7953
8637
  const observer = new MutationObserver(() => {
7954
- const input = formRoot.querySelector(
7955
- `[name="${dependencyFieldPath}"]`
7956
- );
8638
+ const input = formRoot.querySelector(`[name="${dependencyFieldPath}"]`);
7957
8639
  if (input) {
7958
8640
  input.addEventListener("change", () => {
7959
8641
  reevaluateEnableIf(wrapper, element, ctx);
@@ -8100,7 +8782,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
8100
8782
  default: {
8101
8783
  const unsupported = document.createElement("div");
8102
8784
  unsupported.className = "text-red-500 text-sm";
8103
- unsupported.textContent = t("unsupportedFieldType", ctx.state, { type: element.type });
8785
+ unsupported.textContent = t("unsupportedFieldType", ctx.state, {
8786
+ type: element.type
8787
+ });
8104
8788
  wrapper.appendChild(unsupported);
8105
8789
  }
8106
8790
  }
@@ -8152,6 +8836,8 @@ var defaultConfig = {
8152
8836
  noFileSelected: "No file selected",
8153
8837
  noFilesSelected: "No files selected",
8154
8838
  downloadButton: "Download",
8839
+ downloadFile: "Download",
8840
+ openInNewTab: "Open in new tab",
8155
8841
  changeButton: "Change",
8156
8842
  placeholderText: "Enter text",
8157
8843
  previewAlt: "Preview",
@@ -8173,6 +8859,8 @@ var defaultConfig = {
8173
8859
  fileCountSingle: "{count} file",
8174
8860
  fileCountPlural: "{count} files",
8175
8861
  fileCountRange: "({min}-{max})",
8862
+ uploadingFile: "Uploading\u2026",
8863
+ filesCounter: "{count}/{max}",
8176
8864
  // Validation errors
8177
8865
  required: "Required",
8178
8866
  minItems: "Minimum {min} items required",
@@ -8214,6 +8902,8 @@ var defaultConfig = {
8214
8902
  noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
8215
8903
  noFilesSelected: "\u041D\u0435\u0442 \u0444\u0430\u0439\u043B\u043E\u0432",
8216
8904
  downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
8905
+ downloadFile: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
8906
+ openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
8217
8907
  changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
8218
8908
  placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
8219
8909
  previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
@@ -8235,6 +8925,8 @@ var defaultConfig = {
8235
8925
  fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
8236
8926
  fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
8237
8927
  fileCountRange: "({min}-{max})",
8928
+ uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
8929
+ filesCounter: "{count}/{max}",
8238
8930
  // Validation errors
8239
8931
  required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
8240
8932
  minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
@@ -9054,7 +9746,10 @@ var FormBuilderInstance = class {
9054
9746
  );
9055
9747
  if (componentResult !== null) {
9056
9748
  errors.push(...componentResult.errors);
9057
- return { value: componentResult.value, spread: !!componentResult.spread };
9749
+ return {
9750
+ value: componentResult.value,
9751
+ spread: !!componentResult.spread
9752
+ };
9058
9753
  }
9059
9754
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
9060
9755
  return { value: null, spread: false };
@@ -9064,10 +9759,7 @@ var FormBuilderInstance = class {
9064
9759
  var _a;
9065
9760
  if (element.enableIf) {
9066
9761
  try {
9067
- const shouldEnable = evaluateEnableCondition(
9068
- element.enableIf,
9069
- data
9070
- );
9762
+ const shouldEnable = evaluateEnableCondition(element.enableIf, data);
9071
9763
  if (!shouldEnable) {
9072
9764
  return;
9073
9765
  }
@@ -9364,7 +10056,10 @@ var FormBuilderInstance = class {
9364
10056
  disabledWrapper.className = "fb-field-wrapper-disabled";
9365
10057
  disabledWrapper.style.display = "none";
9366
10058
  disabledWrapper.setAttribute("data-field-key", element.key);
9367
- disabledWrapper.setAttribute("data-conditionally-disabled", "true");
10059
+ disabledWrapper.setAttribute(
10060
+ "data-conditionally-disabled",
10061
+ "true"
10062
+ );
9368
10063
  (_c = wrapper.parentNode) == null ? void 0 : _c.replaceChild(disabledWrapper, wrapper);
9369
10064
  }
9370
10065
  } catch (error) {