@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.
- package/README.md +68 -14
- package/dist/browser/formbuilder.min.js +461 -193
- package/dist/browser/formbuilder.v0.2.27.min.js +874 -0
- package/dist/cjs/index.cjs +1793 -1098
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1773 -1091
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +461 -193
- package/dist/types/components/file/constraints.d.ts +26 -0
- package/dist/types/components/file/dom.d.ts +44 -0
- package/dist/types/components/file/preview.d.ts +69 -0
- package/dist/types/components/file/render-edit.d.ts +15 -0
- package/dist/types/components/file/render-readonly.d.ts +23 -0
- package/dist/types/components/file/styles.d.ts +1 -0
- package/dist/types/components/file/upload.d.ts +13 -0
- package/dist/types/components/file/validate.d.ts +13 -0
- package/dist/types/components/file.d.ts +5 -27
- package/dist/types/types/config.d.ts +4 -0
- package/dist/types/types/schema.d.ts +2 -0
- package/dist/types/utils/helpers.d.ts +6 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.25.min.js +0 -606
package/dist/cjs/index.cjs
CHANGED
|
@@ -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(
|
|
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: ${
|
|
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 =
|
|
439
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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: ${
|
|
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 =
|
|
520
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
830
|
-
if (!
|
|
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 ||
|
|
847
|
+
if (element.autoExpand || readonly) {
|
|
839
848
|
applyAutoExpand(textareaInput);
|
|
840
849
|
}
|
|
841
850
|
textareaWrapper.appendChild(textareaInput);
|
|
842
|
-
if (!
|
|
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 =
|
|
882
|
-
if (!
|
|
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 ||
|
|
900
|
+
if (element.autoExpand || readonly) {
|
|
891
901
|
applyAutoExpand(textareaInput);
|
|
892
902
|
}
|
|
893
903
|
textareaContainer.appendChild(textareaInput);
|
|
894
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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
|
|
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 =
|
|
1069
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
1121
|
-
if (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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="
|
|
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="
|
|
2150
|
-
<button class="
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
2209
|
-
|
|
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
|
-
|
|
2242
|
-
|
|
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
|
|
2247
|
-
|
|
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
|
|
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
|
|
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
|
|
2273
|
-
renderUploadedVideoPreview(container, thumbnailUrl,
|
|
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
|
-
|
|
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 = (
|
|
2337
|
-
const
|
|
2338
|
-
const
|
|
2339
|
-
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2346
|
-
const
|
|
2347
|
-
const
|
|
2348
|
-
|
|
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
|
|
2352
|
-
if (
|
|
2353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2370
|
-
<
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
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
|
-
|
|
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
|
|
2387
|
-
|
|
2388
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
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
|
-
|
|
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
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
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
|
|
2547
|
-
var _a, _b;
|
|
2548
|
-
if (
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
3152
|
+
tile.appendChild(img);
|
|
3153
|
+
attachZoomHover(tile, url, meta.name, actionsEl != null ? actionsEl : null);
|
|
2569
3154
|
} else {
|
|
2570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2594
|
-
<
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
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
|
-
|
|
2611
|
-
<
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
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
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
if (
|
|
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
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
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
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
if (
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
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
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
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
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
if (
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
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
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
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
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
);
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
);
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
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
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
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
|
|
3039
|
-
var _a, _b;
|
|
3573
|
+
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3574
|
+
var _a, _b, _c;
|
|
3040
3575
|
const state = ctx.state;
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
}
|
|
3059
|
-
|
|
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
|
-
|
|
3601
|
+
buildDeps(),
|
|
3084
3602
|
ctx.instance,
|
|
3085
3603
|
allowedExts,
|
|
3086
3604
|
maxSizeMB
|
|
3087
3605
|
);
|
|
3088
3606
|
}
|
|
3089
|
-
}
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
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
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
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
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
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
|
|
3672
|
+
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
3130
3673
|
var _a, _b;
|
|
3131
3674
|
const state = ctx.state;
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
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
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
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
|
|
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
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
const
|
|
3251
|
-
|
|
3252
|
-
|
|
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
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
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
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
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
|
|
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
|
|
3298
|
-
const
|
|
3299
|
-
|
|
3300
|
-
const
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
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
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
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
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
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
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3422
|
-
`
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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(
|
|
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
|
-
|
|
4733
|
+
readonly
|
|
4096
4734
|
);
|
|
4097
4735
|
wrapper.appendChild(sliderUI);
|
|
4098
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
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(
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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)
|
|
4981
|
-
|
|
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)
|
|
5019
|
-
|
|
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({
|
|
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({
|
|
6027
|
+
hiddenInput.value = JSON.stringify({
|
|
6028
|
+
[cellsKey]: cells,
|
|
6029
|
+
[mergesKey]: merges
|
|
6030
|
+
});
|
|
5369
6031
|
if (instance) {
|
|
5370
|
-
instance.triggerOnChange(pathKey, {
|
|
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(
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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(
|
|
6979
|
+
console.warn(
|
|
6980
|
+
`Table "${element.key}": invalid prefill merges stripped (${err})`
|
|
6981
|
+
);
|
|
6299
6982
|
initialData = { ...initialData, merges: [] };
|
|
6300
6983
|
}
|
|
6301
6984
|
}
|
|
6302
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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, {
|
|
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 {
|
|
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(
|
|
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) {
|