@dmitryvim/form-builder 0.2.32 → 0.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/formbuilder.min.js +189 -175
- package/dist/browser/formbuilder.v0.2.34.min.js +1162 -0
- package/dist/cjs/index.cjs +584 -496
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +579 -493
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +189 -175
- package/dist/types/components/colour.d.ts +1 -0
- package/dist/types/components/file/upload.d.ts +4 -0
- package/dist/types/components/file.d.ts +2 -2
- package/dist/types/types/config.d.ts +1 -0
- package/dist/types/types/schema.d.ts +8 -0
- package/dist/types/utils/styles.d.ts +42 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.32.min.js +0 -1148
package/dist/esm/index.js
CHANGED
|
@@ -398,6 +398,167 @@ function deepEqual(a, b) {
|
|
|
398
398
|
return a === b;
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
+
// src/utils/styles.ts
|
|
402
|
+
function mountCounterInLabel(wrapper, counter) {
|
|
403
|
+
const labelRow = wrapper.querySelector(
|
|
404
|
+
":scope > [data-fb-label-row]"
|
|
405
|
+
);
|
|
406
|
+
if (labelRow) labelRow.appendChild(counter);
|
|
407
|
+
}
|
|
408
|
+
function createAddItemRow(classNameSuffix, onClick, options = {}) {
|
|
409
|
+
const label = options.label ?? "";
|
|
410
|
+
const showCounter = options.showCounter !== false;
|
|
411
|
+
const row = document.createElement("div");
|
|
412
|
+
row.className = "fb-add-row mt-2";
|
|
413
|
+
row.style.cssText = "display:flex;align-items:stretch;width:100%;";
|
|
414
|
+
const button = document.createElement("button");
|
|
415
|
+
button.type = "button";
|
|
416
|
+
button.className = `add-${classNameSuffix}-btn`;
|
|
417
|
+
button.style.cssText = `
|
|
418
|
+
flex: 1 1 auto;
|
|
419
|
+
display: inline-flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
justify-content: center;
|
|
422
|
+
gap: 6px;
|
|
423
|
+
padding: 6px 10px;
|
|
424
|
+
border: 1px dashed var(--fb-primary-color);
|
|
425
|
+
border-radius: var(--fb-border-radius);
|
|
426
|
+
background: transparent;
|
|
427
|
+
color: var(--fb-primary-color);
|
|
428
|
+
font-size: var(--fb-font-size-small, var(--fb-font-size));
|
|
429
|
+
font-weight: 500;
|
|
430
|
+
font-family: var(--fb-font-family);
|
|
431
|
+
cursor: pointer;
|
|
432
|
+
transition: border-color var(--fb-transition-duration), color var(--fb-transition-duration), background-color var(--fb-transition-duration);
|
|
433
|
+
`;
|
|
434
|
+
button.textContent = label ? `+ ${label}` : "+";
|
|
435
|
+
button.addEventListener("mouseenter", () => {
|
|
436
|
+
if (button.disabled) return;
|
|
437
|
+
button.style.borderStyle = "solid";
|
|
438
|
+
button.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
439
|
+
});
|
|
440
|
+
button.addEventListener("mouseleave", () => {
|
|
441
|
+
button.style.borderStyle = "dashed";
|
|
442
|
+
button.style.backgroundColor = "transparent";
|
|
443
|
+
});
|
|
444
|
+
button.onclick = onClick;
|
|
445
|
+
const counter = document.createElement("span");
|
|
446
|
+
counter.className = "fb-add-counter";
|
|
447
|
+
counter.style.cssText = `
|
|
448
|
+
margin-left: auto;
|
|
449
|
+
font-size: var(--fb-font-size-small, 0.875rem);
|
|
450
|
+
color: var(--fb-text-secondary-color);
|
|
451
|
+
font-weight: 400;
|
|
452
|
+
`;
|
|
453
|
+
if (!showCounter) counter.style.display = "none";
|
|
454
|
+
row.appendChild(button);
|
|
455
|
+
const update = (current, max) => {
|
|
456
|
+
const reached = current >= max;
|
|
457
|
+
row.style.display = reached ? "none" : "flex";
|
|
458
|
+
button.style.display = reached ? "none" : "inline-flex";
|
|
459
|
+
button.disabled = reached;
|
|
460
|
+
if (showCounter) {
|
|
461
|
+
counter.textContent = `${current}/${max === Infinity ? "\u221E" : max}`;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
return { row, button, counter, update };
|
|
465
|
+
}
|
|
466
|
+
function createSlideAddTile(onClick, options = {}) {
|
|
467
|
+
const label = options.label ?? "";
|
|
468
|
+
const tile = document.createElement("button");
|
|
469
|
+
tile.type = "button";
|
|
470
|
+
tile.className = "add-container-btn fb-slide-add";
|
|
471
|
+
tile.style.cssText = `
|
|
472
|
+
display: flex;
|
|
473
|
+
flex-direction: column;
|
|
474
|
+
align-items: center;
|
|
475
|
+
justify-content: center;
|
|
476
|
+
gap: 12px;
|
|
477
|
+
width: 100%;
|
|
478
|
+
min-height: 180px;
|
|
479
|
+
align-self: stretch;
|
|
480
|
+
padding: 24px 16px;
|
|
481
|
+
border: 1.5px dashed var(--fb-primary-color);
|
|
482
|
+
border-radius: var(--fb-border-radius);
|
|
483
|
+
background: transparent;
|
|
484
|
+
color: var(--fb-primary-color);
|
|
485
|
+
font-size: var(--fb-font-size-small, var(--fb-font-size));
|
|
486
|
+
font-weight: 500;
|
|
487
|
+
font-family: var(--fb-font-family);
|
|
488
|
+
cursor: pointer;
|
|
489
|
+
transition: border-color var(--fb-transition-duration), color var(--fb-transition-duration), background-color var(--fb-transition-duration);
|
|
490
|
+
`;
|
|
491
|
+
const circle = document.createElement("span");
|
|
492
|
+
circle.className = "fb-slide-add-circle";
|
|
493
|
+
circle.style.cssText = `
|
|
494
|
+
display: inline-flex;
|
|
495
|
+
align-items: center;
|
|
496
|
+
justify-content: center;
|
|
497
|
+
width: 36px;
|
|
498
|
+
height: 36px;
|
|
499
|
+
border: 1px solid var(--fb-primary-color);
|
|
500
|
+
border-radius: 50%;
|
|
501
|
+
background: var(--fb-background-color);
|
|
502
|
+
font-size: 20px;
|
|
503
|
+
line-height: 1;
|
|
504
|
+
color: inherit;
|
|
505
|
+
transition: inherit;
|
|
506
|
+
`;
|
|
507
|
+
circle.textContent = "+";
|
|
508
|
+
tile.appendChild(circle);
|
|
509
|
+
if (label) {
|
|
510
|
+
const text = document.createElement("span");
|
|
511
|
+
text.textContent = label;
|
|
512
|
+
tile.appendChild(text);
|
|
513
|
+
}
|
|
514
|
+
tile.addEventListener("mouseenter", () => {
|
|
515
|
+
if (tile.disabled) return;
|
|
516
|
+
tile.style.borderStyle = "solid";
|
|
517
|
+
tile.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
518
|
+
});
|
|
519
|
+
tile.addEventListener("mouseleave", () => {
|
|
520
|
+
tile.style.borderStyle = "dashed";
|
|
521
|
+
tile.style.backgroundColor = "transparent";
|
|
522
|
+
});
|
|
523
|
+
tile.onclick = onClick;
|
|
524
|
+
const counter = document.createElement("span");
|
|
525
|
+
counter.className = "fb-add-counter";
|
|
526
|
+
counter.style.cssText = `
|
|
527
|
+
margin-left: auto;
|
|
528
|
+
font-size: var(--fb-font-size-small, 0.875rem);
|
|
529
|
+
color: var(--fb-text-secondary-color);
|
|
530
|
+
font-weight: 400;
|
|
531
|
+
`;
|
|
532
|
+
const update = (current, max) => {
|
|
533
|
+
const reached = current >= max;
|
|
534
|
+
tile.style.display = reached ? "none" : "flex";
|
|
535
|
+
tile.disabled = reached;
|
|
536
|
+
counter.textContent = `${current}/${max === Infinity ? "\u221E" : max}`;
|
|
537
|
+
};
|
|
538
|
+
return { tile, counter, update };
|
|
539
|
+
}
|
|
540
|
+
function applyActionButtonStyles(button, isFormLevel = false) {
|
|
541
|
+
button.style.cssText = `
|
|
542
|
+
background-color: var(--fb-action-bg-color);
|
|
543
|
+
color: var(--fb-action-text-color);
|
|
544
|
+
border: var(--fb-border-width) solid var(--fb-action-border-color);
|
|
545
|
+
padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
|
|
546
|
+
font-size: var(--fb-font-size);
|
|
547
|
+
font-weight: var(--fb-font-weight-medium);
|
|
548
|
+
border-radius: var(--fb-border-radius);
|
|
549
|
+
transition: all var(--fb-transition-duration);
|
|
550
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
551
|
+
`;
|
|
552
|
+
button.addEventListener("mouseenter", () => {
|
|
553
|
+
button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
|
|
554
|
+
button.style.borderColor = "var(--fb-action-hover-border-color)";
|
|
555
|
+
});
|
|
556
|
+
button.addEventListener("mouseleave", () => {
|
|
557
|
+
button.style.backgroundColor = "var(--fb-action-bg-color)";
|
|
558
|
+
button.style.borderColor = "var(--fb-action-border-color)";
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
401
562
|
// src/components/text.ts
|
|
402
563
|
function createCharCounter(element, input, isTextarea = false) {
|
|
403
564
|
const counter = document.createElement("span");
|
|
@@ -450,12 +611,13 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
450
611
|
const readonly = isElementReadonly(element, state, ctx);
|
|
451
612
|
const inputWrapper = document.createElement("div");
|
|
452
613
|
inputWrapper.style.cssText = "position: relative;";
|
|
614
|
+
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
453
615
|
const textInput = document.createElement("input");
|
|
454
616
|
textInput.type = "text";
|
|
455
617
|
textInput.className = "w-full rounded-lg";
|
|
456
618
|
textInput.style.cssText = `
|
|
457
619
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
458
|
-
padding-right: 60px;
|
|
620
|
+
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
459
621
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
460
622
|
border-radius: var(--fb-border-radius);
|
|
461
623
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -500,7 +662,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
500
662
|
textInput.addEventListener("input", handleChange);
|
|
501
663
|
}
|
|
502
664
|
inputWrapper.appendChild(textInput);
|
|
503
|
-
if (
|
|
665
|
+
if (hasCharCounter) {
|
|
504
666
|
const counter = createCharCounter(element, textInput, false);
|
|
505
667
|
inputWrapper.appendChild(counter);
|
|
506
668
|
}
|
|
@@ -511,6 +673,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
511
673
|
const readonly = isElementReadonly(element, state, ctx);
|
|
512
674
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
513
675
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
676
|
+
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
514
677
|
const minCount = element.minCount ?? 1;
|
|
515
678
|
const maxCount = element.maxCount ?? Infinity;
|
|
516
679
|
while (values.length < minCount) {
|
|
@@ -537,7 +700,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
537
700
|
textInput.type = "text";
|
|
538
701
|
textInput.style.cssText = `
|
|
539
702
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
540
|
-
padding-right: 60px;
|
|
703
|
+
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
541
704
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
542
705
|
border-radius: var(--fb-border-radius);
|
|
543
706
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -581,7 +744,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
581
744
|
textInput.addEventListener("input", handleChange);
|
|
582
745
|
}
|
|
583
746
|
inputContainer.appendChild(textInput);
|
|
584
|
-
if (
|
|
747
|
+
if (hasCharCounter) {
|
|
585
748
|
const counter = createCharCounter(element, textInput, false);
|
|
586
749
|
inputContainer.appendChild(counter);
|
|
587
750
|
}
|
|
@@ -638,50 +801,24 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
638
801
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
639
802
|
});
|
|
640
803
|
}
|
|
641
|
-
let
|
|
642
|
-
let countDisplay = null;
|
|
804
|
+
let addUpdate = null;
|
|
643
805
|
if (!readonly) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
658
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
659
|
-
});
|
|
660
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
661
|
-
addBtn.style.backgroundColor = "transparent";
|
|
662
|
-
});
|
|
663
|
-
addBtn.onclick = () => {
|
|
664
|
-
values.push(element.default || "");
|
|
665
|
-
addTextItem(element.default || "");
|
|
666
|
-
updateAddButton();
|
|
667
|
-
updateRemoveButtons();
|
|
668
|
-
};
|
|
669
|
-
countDisplay = document.createElement("span");
|
|
670
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
671
|
-
addRow.appendChild(addBtn);
|
|
672
|
-
addRow.appendChild(countDisplay);
|
|
673
|
-
wrapper.appendChild(addRow);
|
|
806
|
+
const handle = createAddItemRow(
|
|
807
|
+
"text",
|
|
808
|
+
() => {
|
|
809
|
+
values.push(element.default || "");
|
|
810
|
+
addTextItem(element.default || "");
|
|
811
|
+
updateAddButton();
|
|
812
|
+
updateRemoveButtons();
|
|
813
|
+
},
|
|
814
|
+
{ label: element.addLabel }
|
|
815
|
+
);
|
|
816
|
+
addUpdate = handle.update;
|
|
817
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
818
|
+
wrapper.appendChild(handle.row);
|
|
674
819
|
}
|
|
675
820
|
function updateAddButton() {
|
|
676
|
-
if (
|
|
677
|
-
const addBtn = addRow.querySelector(".add-text-btn");
|
|
678
|
-
if (addBtn) {
|
|
679
|
-
const disabled = values.length >= maxCount;
|
|
680
|
-
addBtn.disabled = disabled;
|
|
681
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
682
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
683
|
-
}
|
|
684
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
821
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
685
822
|
}
|
|
686
823
|
values.forEach((value) => addTextItem(value));
|
|
687
824
|
updateAddButton();
|
|
@@ -977,52 +1114,24 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
977
1114
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
978
1115
|
});
|
|
979
1116
|
}
|
|
980
|
-
let
|
|
981
|
-
let countDisplay = null;
|
|
1117
|
+
let addUpdate = null;
|
|
982
1118
|
if (!readonly) {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
997
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
998
|
-
});
|
|
999
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1000
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1001
|
-
});
|
|
1002
|
-
addBtn.onclick = () => {
|
|
1003
|
-
values.push(element.default || "");
|
|
1004
|
-
addTextareaItem(element.default || "");
|
|
1005
|
-
updateAddButton();
|
|
1006
|
-
updateRemoveButtons();
|
|
1007
|
-
};
|
|
1008
|
-
countDisplay = document.createElement("span");
|
|
1009
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1010
|
-
addRow.appendChild(addBtn);
|
|
1011
|
-
addRow.appendChild(countDisplay);
|
|
1012
|
-
wrapper.appendChild(addRow);
|
|
1119
|
+
const handle = createAddItemRow(
|
|
1120
|
+
"textarea",
|
|
1121
|
+
() => {
|
|
1122
|
+
values.push(element.default || "");
|
|
1123
|
+
addTextareaItem(element.default || "");
|
|
1124
|
+
updateAddButton();
|
|
1125
|
+
updateRemoveButtons();
|
|
1126
|
+
},
|
|
1127
|
+
{ label: element.addLabel }
|
|
1128
|
+
);
|
|
1129
|
+
addUpdate = handle.update;
|
|
1130
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
1131
|
+
wrapper.appendChild(handle.row);
|
|
1013
1132
|
}
|
|
1014
1133
|
function updateAddButton() {
|
|
1015
|
-
if (
|
|
1016
|
-
const addBtn = addRow.querySelector(
|
|
1017
|
-
".add-textarea-btn"
|
|
1018
|
-
);
|
|
1019
|
-
if (addBtn) {
|
|
1020
|
-
const disabled = values.length >= maxCount;
|
|
1021
|
-
addBtn.disabled = disabled;
|
|
1022
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1023
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1024
|
-
}
|
|
1025
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1134
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1026
1135
|
}
|
|
1027
1136
|
values.forEach((value) => addTextareaItem(value));
|
|
1028
1137
|
updateAddButton();
|
|
@@ -1228,50 +1337,24 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1228
1337
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1229
1338
|
});
|
|
1230
1339
|
}
|
|
1231
|
-
let
|
|
1232
|
-
let countDisplay = null;
|
|
1340
|
+
let addUpdate = null;
|
|
1233
1341
|
if (!readonly) {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
1248
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1249
|
-
});
|
|
1250
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1251
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1252
|
-
});
|
|
1253
|
-
addBtn.onclick = () => {
|
|
1254
|
-
values.push(element.default || "");
|
|
1255
|
-
addNumberItem(element.default || "");
|
|
1256
|
-
updateAddButton();
|
|
1257
|
-
updateRemoveButtons();
|
|
1258
|
-
};
|
|
1259
|
-
countDisplay = document.createElement("span");
|
|
1260
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1261
|
-
addRow.appendChild(addBtn);
|
|
1262
|
-
addRow.appendChild(countDisplay);
|
|
1263
|
-
wrapper.appendChild(addRow);
|
|
1342
|
+
const handle = createAddItemRow(
|
|
1343
|
+
"number",
|
|
1344
|
+
() => {
|
|
1345
|
+
values.push(element.default || "");
|
|
1346
|
+
addNumberItem(element.default || "");
|
|
1347
|
+
updateAddButton();
|
|
1348
|
+
updateRemoveButtons();
|
|
1349
|
+
},
|
|
1350
|
+
{ label: element.addLabel }
|
|
1351
|
+
);
|
|
1352
|
+
addUpdate = handle.update;
|
|
1353
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
1354
|
+
wrapper.appendChild(handle.row);
|
|
1264
1355
|
}
|
|
1265
1356
|
function updateAddButton() {
|
|
1266
|
-
if (
|
|
1267
|
-
const addBtn = addRow.querySelector(".add-number-btn");
|
|
1268
|
-
if (addBtn) {
|
|
1269
|
-
const disabled = values.length >= maxCount;
|
|
1270
|
-
addBtn.disabled = disabled;
|
|
1271
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1272
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1273
|
-
}
|
|
1274
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1357
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1275
1358
|
}
|
|
1276
1359
|
values.forEach((value) => addNumberItem(value));
|
|
1277
1360
|
updateAddButton();
|
|
@@ -1556,51 +1639,25 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1556
1639
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1557
1640
|
});
|
|
1558
1641
|
}
|
|
1559
|
-
let
|
|
1560
|
-
let countDisplay = null;
|
|
1642
|
+
let addUpdate = null;
|
|
1561
1643
|
if (!readonly) {
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1577
|
-
});
|
|
1578
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1579
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1580
|
-
});
|
|
1581
|
-
addBtn.onclick = () => {
|
|
1582
|
-
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1583
|
-
values.push(defaultValue);
|
|
1584
|
-
addSelectItem(defaultValue);
|
|
1585
|
-
updateAddButton();
|
|
1586
|
-
updateRemoveButtons();
|
|
1587
|
-
};
|
|
1588
|
-
countDisplay = document.createElement("span");
|
|
1589
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1590
|
-
addRow.appendChild(addBtn);
|
|
1591
|
-
addRow.appendChild(countDisplay);
|
|
1592
|
-
wrapper.appendChild(addRow);
|
|
1644
|
+
const handle = createAddItemRow(
|
|
1645
|
+
"select",
|
|
1646
|
+
() => {
|
|
1647
|
+
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1648
|
+
values.push(defaultValue);
|
|
1649
|
+
addSelectItem(defaultValue);
|
|
1650
|
+
updateAddButton();
|
|
1651
|
+
updateRemoveButtons();
|
|
1652
|
+
},
|
|
1653
|
+
{ label: element.addLabel }
|
|
1654
|
+
);
|
|
1655
|
+
addUpdate = handle.update;
|
|
1656
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
1657
|
+
wrapper.appendChild(handle.row);
|
|
1593
1658
|
}
|
|
1594
1659
|
function updateAddButton() {
|
|
1595
|
-
if (
|
|
1596
|
-
const addBtn = addRow.querySelector(".add-select-btn");
|
|
1597
|
-
if (addBtn) {
|
|
1598
|
-
const disabled = values.length >= maxCount;
|
|
1599
|
-
addBtn.disabled = disabled;
|
|
1600
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1601
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1602
|
-
}
|
|
1603
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1660
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1604
1661
|
}
|
|
1605
1662
|
values.forEach((value) => addSelectItem(value));
|
|
1606
1663
|
updateAddButton();
|
|
@@ -1943,53 +2000,25 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
1943
2000
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1944
2001
|
});
|
|
1945
2002
|
}
|
|
1946
|
-
let
|
|
1947
|
-
let countDisplay = null;
|
|
2003
|
+
let addUpdate = null;
|
|
1948
2004
|
if (!readonly) {
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1964
|
-
});
|
|
1965
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1966
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1967
|
-
});
|
|
1968
|
-
addBtn.onclick = () => {
|
|
1969
|
-
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1970
|
-
values.push(defaultValue);
|
|
1971
|
-
addSwitcherItem(defaultValue);
|
|
1972
|
-
updateAddButton();
|
|
1973
|
-
updateRemoveButtons();
|
|
1974
|
-
};
|
|
1975
|
-
countDisplay = document.createElement("span");
|
|
1976
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1977
|
-
addRow.appendChild(addBtn);
|
|
1978
|
-
addRow.appendChild(countDisplay);
|
|
1979
|
-
wrapper.appendChild(addRow);
|
|
2005
|
+
const handle = createAddItemRow(
|
|
2006
|
+
"switcher",
|
|
2007
|
+
() => {
|
|
2008
|
+
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
2009
|
+
values.push(defaultValue);
|
|
2010
|
+
addSwitcherItem(defaultValue);
|
|
2011
|
+
updateAddButton();
|
|
2012
|
+
updateRemoveButtons();
|
|
2013
|
+
},
|
|
2014
|
+
{ label: element.addLabel }
|
|
2015
|
+
);
|
|
2016
|
+
addUpdate = handle.update;
|
|
2017
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
2018
|
+
wrapper.appendChild(handle.row);
|
|
1980
2019
|
}
|
|
1981
2020
|
function updateAddButton() {
|
|
1982
|
-
if (
|
|
1983
|
-
const addBtn = addRow.querySelector(
|
|
1984
|
-
".add-switcher-btn"
|
|
1985
|
-
);
|
|
1986
|
-
if (addBtn) {
|
|
1987
|
-
const disabled = values.length >= maxCount;
|
|
1988
|
-
addBtn.disabled = disabled;
|
|
1989
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1990
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1991
|
-
}
|
|
1992
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
2021
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1993
2022
|
}
|
|
1994
2023
|
values.forEach((value) => addSwitcherItem(value));
|
|
1995
2024
|
updateAddButton();
|
|
@@ -2857,24 +2886,40 @@ function createTileActions(options) {
|
|
|
2857
2886
|
return btn;
|
|
2858
2887
|
};
|
|
2859
2888
|
if (replaceHandler) {
|
|
2860
|
-
const replaceBtn = makeBtn(
|
|
2889
|
+
const replaceBtn = makeBtn(
|
|
2890
|
+
ICON_REPLACE,
|
|
2891
|
+
t("replaceFile", state),
|
|
2892
|
+
"fb-tile-action-replace"
|
|
2893
|
+
);
|
|
2861
2894
|
replaceBtn.addEventListener("click", () => replaceHandler());
|
|
2862
2895
|
group.appendChild(replaceBtn);
|
|
2863
2896
|
}
|
|
2864
2897
|
if (libraryHandler) {
|
|
2865
|
-
const libBtn = makeBtn(
|
|
2898
|
+
const libBtn = makeBtn(
|
|
2899
|
+
ICON_LIBRARY,
|
|
2900
|
+
t("fromLibrary", state),
|
|
2901
|
+
"fb-tile-action-library"
|
|
2902
|
+
);
|
|
2866
2903
|
libBtn.addEventListener("click", () => libraryHandler());
|
|
2867
2904
|
group.appendChild(libBtn);
|
|
2868
2905
|
}
|
|
2869
2906
|
if (canDownload(state, meta)) {
|
|
2870
|
-
const dlBtn = makeBtn(
|
|
2907
|
+
const dlBtn = makeBtn(
|
|
2908
|
+
ICON_DOWNLOAD,
|
|
2909
|
+
t("downloadFile", state),
|
|
2910
|
+
"fb-tile-action-download"
|
|
2911
|
+
);
|
|
2871
2912
|
dlBtn.addEventListener("click", () => {
|
|
2872
2913
|
triggerTileDownload(resourceId, fileName, state, meta);
|
|
2873
2914
|
});
|
|
2874
2915
|
group.appendChild(dlBtn);
|
|
2875
2916
|
}
|
|
2876
2917
|
if (canOpenInTab(state, meta)) {
|
|
2877
|
-
const openBtn = makeBtn(
|
|
2918
|
+
const openBtn = makeBtn(
|
|
2919
|
+
ICON_OPEN,
|
|
2920
|
+
t("openInNewTab", state),
|
|
2921
|
+
"fb-tile-action-open"
|
|
2922
|
+
);
|
|
2878
2923
|
openBtn.addEventListener("click", () => {
|
|
2879
2924
|
triggerTileOpen(resourceId, state, meta).catch((err) => {
|
|
2880
2925
|
console.error("Open failed:", err);
|
|
@@ -2883,7 +2928,11 @@ function createTileActions(options) {
|
|
|
2883
2928
|
group.appendChild(openBtn);
|
|
2884
2929
|
}
|
|
2885
2930
|
if (canRemove && removeHandler) {
|
|
2886
|
-
const rmBtn = makeBtn(
|
|
2931
|
+
const rmBtn = makeBtn(
|
|
2932
|
+
ICON_REMOVE,
|
|
2933
|
+
t("removeElement", state),
|
|
2934
|
+
"fb-tile-action-remove"
|
|
2935
|
+
);
|
|
2887
2936
|
rmBtn.addEventListener("click", () => {
|
|
2888
2937
|
removeHandler();
|
|
2889
2938
|
});
|
|
@@ -2961,11 +3010,17 @@ function positionZoomPopup(popup, tile) {
|
|
|
2961
3010
|
} else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
|
|
2962
3011
|
top = tileRect.bottom + margin;
|
|
2963
3012
|
} else {
|
|
2964
|
-
top = Math.max(
|
|
3013
|
+
top = Math.max(
|
|
3014
|
+
padding,
|
|
3015
|
+
Math.min(window.innerHeight - popupSize - padding, tileRect.top)
|
|
3016
|
+
);
|
|
2965
3017
|
}
|
|
2966
3018
|
const tileCenterX = tileRect.left + tileRect.width / 2;
|
|
2967
3019
|
let left = tileCenterX - popupSize / 2;
|
|
2968
|
-
left = Math.max(
|
|
3020
|
+
left = Math.max(
|
|
3021
|
+
padding,
|
|
3022
|
+
Math.min(window.innerWidth - popupSize - padding, left)
|
|
3023
|
+
);
|
|
2969
3024
|
popup.style.top = `${top}px`;
|
|
2970
3025
|
popup.style.left = `${left}px`;
|
|
2971
3026
|
}
|
|
@@ -3009,7 +3064,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
|
|
|
3009
3064
|
const popup = getOrCreateZoomPopup();
|
|
3010
3065
|
const existingActions = popup.querySelector(".fb-tile-actions");
|
|
3011
3066
|
if (existingActions) existingActions.remove();
|
|
3012
|
-
const img = popup.querySelector(
|
|
3067
|
+
const img = popup.querySelector(
|
|
3068
|
+
".fb-tile-zoom-preview-img"
|
|
3069
|
+
);
|
|
3013
3070
|
img.src = src;
|
|
3014
3071
|
img.alt = alt;
|
|
3015
3072
|
if (actionsEl) {
|
|
@@ -3037,7 +3094,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
|
|
|
3037
3094
|
});
|
|
3038
3095
|
}
|
|
3039
3096
|
function attachClonedActionListeners(cloned, original) {
|
|
3040
|
-
const originalBtns = Array.from(
|
|
3097
|
+
const originalBtns = Array.from(
|
|
3098
|
+
original.querySelectorAll(".fb-tile-action-btn")
|
|
3099
|
+
);
|
|
3041
3100
|
const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
|
|
3042
3101
|
clonedBtns.forEach((clonedBtn, i) => {
|
|
3043
3102
|
const origBtn = originalBtns[i];
|
|
@@ -3090,14 +3149,18 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
3090
3149
|
return newContainer;
|
|
3091
3150
|
}
|
|
3092
3151
|
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
3093
|
-
const changeBtn = container.querySelector(
|
|
3152
|
+
const changeBtn = container.querySelector(
|
|
3153
|
+
".change-file-btn"
|
|
3154
|
+
);
|
|
3094
3155
|
if (changeBtn) {
|
|
3095
3156
|
changeBtn.onclick = (e) => {
|
|
3096
3157
|
e.stopPropagation();
|
|
3097
3158
|
deps?.picker?.click();
|
|
3098
3159
|
};
|
|
3099
3160
|
}
|
|
3100
|
-
const deleteBtn = container.querySelector(
|
|
3161
|
+
const deleteBtn = container.querySelector(
|
|
3162
|
+
".delete-file-btn"
|
|
3163
|
+
);
|
|
3101
3164
|
if (deleteBtn) {
|
|
3102
3165
|
deleteBtn.onclick = (e) => {
|
|
3103
3166
|
e.stopPropagation();
|
|
@@ -3230,7 +3293,13 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
3230
3293
|
deps
|
|
3231
3294
|
);
|
|
3232
3295
|
} else {
|
|
3233
|
-
await renderUploadedFilePreview(
|
|
3296
|
+
await renderUploadedFilePreview(
|
|
3297
|
+
container,
|
|
3298
|
+
resourceId,
|
|
3299
|
+
fileName,
|
|
3300
|
+
meta,
|
|
3301
|
+
state
|
|
3302
|
+
);
|
|
3234
3303
|
const isVideo = meta?.type?.startsWith("video/");
|
|
3235
3304
|
if (!isReadonly && !isVideo) {
|
|
3236
3305
|
renderDeleteButton(container, resourceId, state);
|
|
@@ -3257,7 +3326,8 @@ async function renderFilePreviewReadonly(resourceId, state, fileName, options =
|
|
|
3257
3326
|
}
|
|
3258
3327
|
const localFileUrl = meta?.file instanceof File ? getLocalFileUrl(meta.file) : null;
|
|
3259
3328
|
const resolveOpenUrl = async () => {
|
|
3260
|
-
if (state.config.getDownloadUrl)
|
|
3329
|
+
if (state.config.getDownloadUrl)
|
|
3330
|
+
return state.config.getDownloadUrl(resourceId);
|
|
3261
3331
|
if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
|
|
3262
3332
|
return localFileUrl;
|
|
3263
3333
|
};
|
|
@@ -3397,7 +3467,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3397
3467
|
}
|
|
3398
3468
|
} catch (error) {
|
|
3399
3469
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3400
|
-
if (state.config.onThumbnailError)
|
|
3470
|
+
if (state.config.onThumbnailError)
|
|
3471
|
+
state.config.onThumbnailError(err, rid);
|
|
3401
3472
|
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>`;
|
|
3402
3473
|
}
|
|
3403
3474
|
} else {
|
|
@@ -3430,7 +3501,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3430
3501
|
}
|
|
3431
3502
|
} catch (error) {
|
|
3432
3503
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3433
|
-
if (state.config.onThumbnailError)
|
|
3504
|
+
if (state.config.onThumbnailError)
|
|
3505
|
+
state.config.onThumbnailError(err, rid);
|
|
3434
3506
|
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>`;
|
|
3435
3507
|
}
|
|
3436
3508
|
} else {
|
|
@@ -3463,7 +3535,8 @@ async function forceDownload(resourceId, fileName, state) {
|
|
|
3463
3535
|
if (fileUrl) {
|
|
3464
3536
|
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
3465
3537
|
const response = await fetch(finalUrl);
|
|
3466
|
-
if (!response.ok)
|
|
3538
|
+
if (!response.ok)
|
|
3539
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3467
3540
|
const blob = await response.blob();
|
|
3468
3541
|
downloadBlob(blob, fileName);
|
|
3469
3542
|
} else {
|
|
@@ -3512,7 +3585,9 @@ async function uploadSingleFile(file, state) {
|
|
|
3512
3585
|
} catch (error) {
|
|
3513
3586
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3514
3587
|
if (state.config.onUploadError) state.config.onUploadError(err, file);
|
|
3515
|
-
|
|
3588
|
+
const wrapped = new Error(`File upload failed: ${err.message}`);
|
|
3589
|
+
wrapped.cause = err;
|
|
3590
|
+
throw wrapped;
|
|
3516
3591
|
}
|
|
3517
3592
|
}
|
|
3518
3593
|
async function handleFileSelect(opts) {
|
|
@@ -3624,7 +3699,9 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3624
3699
|
const rejectedBySize = afterMime.filter(
|
|
3625
3700
|
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3626
3701
|
);
|
|
3627
|
-
const valid = afterMime.filter(
|
|
3702
|
+
const valid = afterMime.filter(
|
|
3703
|
+
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
3704
|
+
);
|
|
3628
3705
|
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3629
3706
|
const accepted = valid.slice(0, remaining);
|
|
3630
3707
|
const skippedByCount = valid.length - accepted.length;
|
|
@@ -3637,7 +3714,13 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3637
3714
|
if (rejectedByMime.length > 0) {
|
|
3638
3715
|
const mimes = constraints.allowedMimes.join(", ");
|
|
3639
3716
|
const names = rejectedByMime.map((f) => f.name).join(", ");
|
|
3640
|
-
errorParts.push(
|
|
3717
|
+
errorParts.push(
|
|
3718
|
+
t("invalidFileMime", state, {
|
|
3719
|
+
name: names,
|
|
3720
|
+
type: rejectedByMime.map((f) => f.type).join(", "),
|
|
3721
|
+
mimes
|
|
3722
|
+
})
|
|
3723
|
+
);
|
|
3641
3724
|
}
|
|
3642
3725
|
if (rejectedBySize.length > 0) {
|
|
3643
3726
|
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
@@ -3661,7 +3744,8 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
|
3661
3744
|
const addTile = tilesWrap.querySelector(".fb-multi-add-tile-js") ?? tilesWrap.querySelector(".fb-tile-add");
|
|
3662
3745
|
if (addTile) addTile.style.display = "none";
|
|
3663
3746
|
}
|
|
3664
|
-
|
|
3747
|
+
const failures = [];
|
|
3748
|
+
await Promise.allSettled(
|
|
3665
3749
|
accepted.map(async (file) => {
|
|
3666
3750
|
const placeholder = createUploadingTile(file.name, state);
|
|
3667
3751
|
if (listEl) {
|
|
@@ -3678,11 +3762,27 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
|
3678
3762
|
file: void 0
|
|
3679
3763
|
});
|
|
3680
3764
|
resourceIds.push(rid);
|
|
3765
|
+
} catch (err) {
|
|
3766
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
3767
|
+
const cause = wrapped.cause;
|
|
3768
|
+
const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
|
|
3769
|
+
failures.push({ file, error: root });
|
|
3681
3770
|
} finally {
|
|
3682
3771
|
placeholder.remove();
|
|
3683
3772
|
}
|
|
3684
3773
|
})
|
|
3685
3774
|
);
|
|
3775
|
+
return { failures };
|
|
3776
|
+
}
|
|
3777
|
+
function buildBatchErrorMessage(filterError, failures, state) {
|
|
3778
|
+
if (failures.length === 0) return filterError;
|
|
3779
|
+
const uploadMsg = failures.map(
|
|
3780
|
+
(f) => t("uploadFailed", state, {
|
|
3781
|
+
name: f.file.name,
|
|
3782
|
+
error: f.error.message
|
|
3783
|
+
})
|
|
3784
|
+
).join(" \u2022 ");
|
|
3785
|
+
return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
|
|
3686
3786
|
}
|
|
3687
3787
|
function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3688
3788
|
setupDragAndDrop(filesContainer, async (files) => {
|
|
@@ -3698,7 +3798,13 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
|
|
|
3698
3798
|
clearFileError(filesContainer);
|
|
3699
3799
|
}
|
|
3700
3800
|
const list = filesContainer.querySelector(".files-list") ?? filesContainer;
|
|
3701
|
-
await uploadBatch(accepted, resourceIds, list, state);
|
|
3801
|
+
const { failures } = await uploadBatch(accepted, resourceIds, list, state);
|
|
3802
|
+
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
3803
|
+
if (combined) {
|
|
3804
|
+
showFileError(filesContainer, combined);
|
|
3805
|
+
} else {
|
|
3806
|
+
clearFileError(filesContainer);
|
|
3807
|
+
}
|
|
3702
3808
|
updateCallback();
|
|
3703
3809
|
if (instance && pathKey && !state.config.readonly) {
|
|
3704
3810
|
instance.triggerOnChange(pathKey, resourceIds);
|
|
@@ -3721,7 +3827,20 @@ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback
|
|
|
3721
3827
|
clearFileError(wrapperEl);
|
|
3722
3828
|
}
|
|
3723
3829
|
const listEl = wrapperEl?.querySelector(".files-list");
|
|
3724
|
-
|
|
3830
|
+
const { failures } = await uploadBatch(
|
|
3831
|
+
accepted,
|
|
3832
|
+
resourceIds,
|
|
3833
|
+
listEl ?? null,
|
|
3834
|
+
state
|
|
3835
|
+
);
|
|
3836
|
+
if (wrapperEl) {
|
|
3837
|
+
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
3838
|
+
if (combined) {
|
|
3839
|
+
showFileError(wrapperEl, combined);
|
|
3840
|
+
} else {
|
|
3841
|
+
clearFileError(wrapperEl);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3725
3844
|
updateCallback();
|
|
3726
3845
|
filesPicker.value = "";
|
|
3727
3846
|
if (instance && pathKey && !state.config.readonly) {
|
|
@@ -3753,10 +3872,17 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
|
|
|
3753
3872
|
}
|
|
3754
3873
|
if (!isMimeAllowed(resource.type, allowedMimes)) {
|
|
3755
3874
|
const mimes = allowedMimes.join(", ");
|
|
3756
|
-
return t("invalidFileMime", state, {
|
|
3875
|
+
return t("invalidFileMime", state, {
|
|
3876
|
+
name: resource.name,
|
|
3877
|
+
type: resource.type,
|
|
3878
|
+
mimes
|
|
3879
|
+
});
|
|
3757
3880
|
}
|
|
3758
3881
|
if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
|
|
3759
|
-
return t("fileTooLarge", state, {
|
|
3882
|
+
return t("fileTooLarge", state, {
|
|
3883
|
+
name: resource.name,
|
|
3884
|
+
maxSize: maxSizeMB
|
|
3885
|
+
});
|
|
3760
3886
|
}
|
|
3761
3887
|
return null;
|
|
3762
3888
|
}
|
|
@@ -3815,7 +3941,13 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
3815
3941
|
return true;
|
|
3816
3942
|
});
|
|
3817
3943
|
const validItems = deduped.filter((r) => {
|
|
3818
|
-
const err = validatePickedResource(
|
|
3944
|
+
const err = validatePickedResource(
|
|
3945
|
+
r,
|
|
3946
|
+
allowedExtensions,
|
|
3947
|
+
allowedMimes,
|
|
3948
|
+
maxSizeMB,
|
|
3949
|
+
state
|
|
3950
|
+
);
|
|
3819
3951
|
return err === null;
|
|
3820
3952
|
});
|
|
3821
3953
|
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
|
|
@@ -3859,14 +3991,22 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
|
|
|
3859
3991
|
}
|
|
3860
3992
|
if (picked.length === 0) return;
|
|
3861
3993
|
const first = picked[0];
|
|
3862
|
-
const validationError = validatePickedResource(
|
|
3994
|
+
const validationError = validatePickedResource(
|
|
3995
|
+
first,
|
|
3996
|
+
allowedExtensions,
|
|
3997
|
+
allowedMimes,
|
|
3998
|
+
maxSizeMB,
|
|
3999
|
+
state
|
|
4000
|
+
);
|
|
3863
4001
|
if (validationError !== null) {
|
|
3864
4002
|
showFileError(container, validationError);
|
|
3865
4003
|
return;
|
|
3866
4004
|
}
|
|
3867
4005
|
clearFileError(container);
|
|
3868
4006
|
registerPickedResource(first, state);
|
|
3869
|
-
let hiddenInput = fileWrapper.querySelector(
|
|
4007
|
+
let hiddenInput = fileWrapper.querySelector(
|
|
4008
|
+
'input[type="hidden"]'
|
|
4009
|
+
);
|
|
3870
4010
|
if (!hiddenInput) {
|
|
3871
4011
|
hiddenInput = document.createElement("input");
|
|
3872
4012
|
hiddenInput.type = "hidden";
|
|
@@ -4025,7 +4165,8 @@ function renderSingleFileFilled(fileContainer, resourceId, state, deps, extras)
|
|
|
4025
4165
|
grid.appendChild(tile);
|
|
4026
4166
|
fileContainer.className = "file-preview-container";
|
|
4027
4167
|
fileContainer.removeAttribute("style");
|
|
4028
|
-
while (fileContainer.firstChild)
|
|
4168
|
+
while (fileContainer.firstChild)
|
|
4169
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
4029
4170
|
fileContainer.appendChild(outer);
|
|
4030
4171
|
}
|
|
4031
4172
|
function buildMultiAddTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false) {
|
|
@@ -4106,7 +4247,9 @@ function buildMetaLine(state, element, ridCount, maxCount, canClearAll, onClearA
|
|
|
4106
4247
|
metaText.appendChild(sizeSpan);
|
|
4107
4248
|
metaText.appendChild(buildMetaDot());
|
|
4108
4249
|
}
|
|
4109
|
-
const exts = getAllowedExtensions(
|
|
4250
|
+
const exts = getAllowedExtensions(
|
|
4251
|
+
element.accept
|
|
4252
|
+
);
|
|
4110
4253
|
if (exts.length > 0) {
|
|
4111
4254
|
const fmtSpan = document.createElement("span");
|
|
4112
4255
|
fmtSpan.className = "fb-meta-mono";
|
|
@@ -4352,7 +4495,9 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4352
4495
|
setupDragAndDrop(container, handlers.dragHandler);
|
|
4353
4496
|
},
|
|
4354
4497
|
onRemove() {
|
|
4355
|
-
const hiddenInput = fileWrapper.querySelector(
|
|
4498
|
+
const hiddenInput = fileWrapper.querySelector(
|
|
4499
|
+
'input[type="hidden"]'
|
|
4500
|
+
);
|
|
4356
4501
|
const currentRid = hiddenInput?.value;
|
|
4357
4502
|
if (currentRid) {
|
|
4358
4503
|
releaseLocalFileUrl(state.resourceIndex.get(currentRid)?.file);
|
|
@@ -4362,7 +4507,9 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4362
4507
|
}
|
|
4363
4508
|
};
|
|
4364
4509
|
const buildSingleExtras = () => {
|
|
4365
|
-
const hasLibrary = Boolean(
|
|
4510
|
+
const hasLibrary = Boolean(
|
|
4511
|
+
state.config.pickExistingFiles && !element.disableLibrary
|
|
4512
|
+
);
|
|
4366
4513
|
return {
|
|
4367
4514
|
replaceHandler: state.config.uploadFile ? () => picker.click() : null,
|
|
4368
4515
|
libraryHandler: hasLibrary ? () => {
|
|
@@ -4374,7 +4521,13 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4374
4521
|
pathKey,
|
|
4375
4522
|
pathKey,
|
|
4376
4523
|
async (rid) => {
|
|
4377
|
-
renderSingleFileFilled(
|
|
4524
|
+
renderSingleFileFilled(
|
|
4525
|
+
fileContainer,
|
|
4526
|
+
rid,
|
|
4527
|
+
state,
|
|
4528
|
+
buildDeps(),
|
|
4529
|
+
buildSingleExtras()
|
|
4530
|
+
);
|
|
4378
4531
|
},
|
|
4379
4532
|
ctx.instance
|
|
4380
4533
|
).catch((err) => {
|
|
@@ -4390,14 +4543,21 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4390
4543
|
setupDrop: handlers.setupDrop,
|
|
4391
4544
|
onRemove: handlers.onRemove,
|
|
4392
4545
|
onAfterUpload: (container, rid) => {
|
|
4393
|
-
renderSingleFileFilled(
|
|
4546
|
+
renderSingleFileFilled(
|
|
4547
|
+
container,
|
|
4548
|
+
rid,
|
|
4549
|
+
state,
|
|
4550
|
+
buildDeps(),
|
|
4551
|
+
buildSingleExtras()
|
|
4552
|
+
);
|
|
4394
4553
|
}
|
|
4395
4554
|
});
|
|
4396
4555
|
const renderEmptySingleState = () => {
|
|
4397
4556
|
ensureFileStyles();
|
|
4398
4557
|
fileContainer.className = "file-preview-container";
|
|
4399
4558
|
fileContainer.removeAttribute("style");
|
|
4400
|
-
while (fileContainer.firstChild)
|
|
4559
|
+
while (fileContainer.firstChild)
|
|
4560
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
4401
4561
|
const onLibraryClick = buildSingleExtras().libraryHandler;
|
|
4402
4562
|
const wideTile = buildWideTile(
|
|
4403
4563
|
state,
|
|
@@ -4538,7 +4698,13 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4538
4698
|
setupMultiFileEditMode(element, ctx, wrapper, pathKey, Infinity);
|
|
4539
4699
|
}
|
|
4540
4700
|
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
4541
|
-
setupMultiFileEditMode(
|
|
4701
|
+
setupMultiFileEditMode(
|
|
4702
|
+
element,
|
|
4703
|
+
ctx,
|
|
4704
|
+
wrapper,
|
|
4705
|
+
pathKey,
|
|
4706
|
+
element.maxCount ?? Infinity
|
|
4707
|
+
);
|
|
4542
4708
|
}
|
|
4543
4709
|
|
|
4544
4710
|
// src/components/file/validate.ts
|
|
@@ -4717,7 +4883,11 @@ function renderMultiFileReadonly(rids, state, wrapper, pathKey, _marginTop) {
|
|
|
4717
4883
|
const placeholder = placeholders[i];
|
|
4718
4884
|
const meta = state.resourceIndex.get(resourceId);
|
|
4719
4885
|
renderFilePreviewReadonly(resourceId, state, meta?.name).then((tile) => {
|
|
4720
|
-
tile.classList.add(
|
|
4886
|
+
tile.classList.add(
|
|
4887
|
+
"fb-readonly-tile",
|
|
4888
|
+
"fb-checker",
|
|
4889
|
+
"fb-tile-resource"
|
|
4890
|
+
);
|
|
4721
4891
|
tile.dataset.resourceId = resourceId;
|
|
4722
4892
|
placeholder.replaceWith(tile);
|
|
4723
4893
|
}).catch(() => {
|
|
@@ -5067,51 +5237,25 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
5067
5237
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5068
5238
|
});
|
|
5069
5239
|
}
|
|
5070
|
-
let
|
|
5071
|
-
let countDisplay = null;
|
|
5240
|
+
let addUpdate = null;
|
|
5072
5241
|
if (!readonly) {
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5088
|
-
});
|
|
5089
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
5090
|
-
addBtn.style.backgroundColor = "transparent";
|
|
5091
|
-
});
|
|
5092
|
-
addBtn.onclick = () => {
|
|
5093
|
-
const defaultColour = element.default || "#000000";
|
|
5094
|
-
values.push(defaultColour);
|
|
5095
|
-
addColourItem(defaultColour);
|
|
5096
|
-
updateAddButton();
|
|
5097
|
-
updateRemoveButtons();
|
|
5098
|
-
};
|
|
5099
|
-
countDisplay = document.createElement("span");
|
|
5100
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
5101
|
-
addRow.appendChild(addBtn);
|
|
5102
|
-
addRow.appendChild(countDisplay);
|
|
5103
|
-
wrapper.appendChild(addRow);
|
|
5242
|
+
const handle = createAddItemRow(
|
|
5243
|
+
"colour",
|
|
5244
|
+
() => {
|
|
5245
|
+
const defaultColour = element.default || "#000000";
|
|
5246
|
+
values.push(defaultColour);
|
|
5247
|
+
addColourItem(defaultColour);
|
|
5248
|
+
updateAddButton();
|
|
5249
|
+
updateRemoveButtons();
|
|
5250
|
+
},
|
|
5251
|
+
{ label: element.addLabel }
|
|
5252
|
+
);
|
|
5253
|
+
addUpdate = handle.update;
|
|
5254
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
5255
|
+
wrapper.appendChild(handle.row);
|
|
5104
5256
|
}
|
|
5105
5257
|
function updateAddButton() {
|
|
5106
|
-
if (
|
|
5107
|
-
const addBtn = addRow.querySelector(".add-colour-btn");
|
|
5108
|
-
if (addBtn) {
|
|
5109
|
-
const disabled = values.length >= maxCount;
|
|
5110
|
-
addBtn.disabled = disabled;
|
|
5111
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
5112
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5113
|
-
}
|
|
5114
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
5258
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
5115
5259
|
}
|
|
5116
5260
|
values.forEach((value) => addColourItem(value));
|
|
5117
5261
|
updateAddButton();
|
|
@@ -5547,50 +5691,24 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
5547
5691
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5548
5692
|
});
|
|
5549
5693
|
}
|
|
5550
|
-
let
|
|
5551
|
-
let countDisplay = null;
|
|
5694
|
+
let addUpdate = null;
|
|
5552
5695
|
if (!readonly) {
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
5567
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5568
|
-
});
|
|
5569
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
5570
|
-
addBtn.style.backgroundColor = "transparent";
|
|
5571
|
-
});
|
|
5572
|
-
addBtn.onclick = () => {
|
|
5573
|
-
values.push(defaultValue);
|
|
5574
|
-
addSliderItem(defaultValue);
|
|
5575
|
-
updateAddButton();
|
|
5576
|
-
updateRemoveButtons();
|
|
5577
|
-
};
|
|
5578
|
-
countDisplay = document.createElement("span");
|
|
5579
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
5580
|
-
addRow.appendChild(addBtn);
|
|
5581
|
-
addRow.appendChild(countDisplay);
|
|
5582
|
-
wrapper.appendChild(addRow);
|
|
5696
|
+
const handle = createAddItemRow(
|
|
5697
|
+
"slider",
|
|
5698
|
+
() => {
|
|
5699
|
+
values.push(defaultValue);
|
|
5700
|
+
addSliderItem(defaultValue);
|
|
5701
|
+
updateAddButton();
|
|
5702
|
+
updateRemoveButtons();
|
|
5703
|
+
},
|
|
5704
|
+
{ label: element.addLabel }
|
|
5705
|
+
);
|
|
5706
|
+
addUpdate = handle.update;
|
|
5707
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
5708
|
+
wrapper.appendChild(handle.row);
|
|
5583
5709
|
}
|
|
5584
5710
|
function updateAddButton() {
|
|
5585
|
-
if (
|
|
5586
|
-
const addBtn = addRow.querySelector(".add-slider-btn");
|
|
5587
|
-
if (addBtn) {
|
|
5588
|
-
const disabled = values.length >= maxCount;
|
|
5589
|
-
addBtn.disabled = disabled;
|
|
5590
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
5591
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5592
|
-
}
|
|
5593
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
5711
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
5594
5712
|
}
|
|
5595
5713
|
values.forEach((value) => addSliderItem(value));
|
|
5596
5714
|
updateAddButton();
|
|
@@ -5937,14 +6055,53 @@ function getChildWrapperClass(isSlides, columns) {
|
|
|
5937
6055
|
const cols = columns || 1;
|
|
5938
6056
|
return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
|
|
5939
6057
|
}
|
|
6058
|
+
function mountRemoveButton(item, onRemove) {
|
|
6059
|
+
const rem = document.createElement("button");
|
|
6060
|
+
rem.type = "button";
|
|
6061
|
+
rem.className = "fb-item-remove";
|
|
6062
|
+
rem.style.cssText = `
|
|
6063
|
+
width: 22px;
|
|
6064
|
+
height: 22px;
|
|
6065
|
+
display: inline-flex;
|
|
6066
|
+
align-items: center;
|
|
6067
|
+
justify-content: center;
|
|
6068
|
+
padding: 0;
|
|
6069
|
+
line-height: 1;
|
|
6070
|
+
font-size: 14px;
|
|
6071
|
+
color: var(--fb-error-color);
|
|
6072
|
+
background-color: transparent;
|
|
6073
|
+
border: 0;
|
|
6074
|
+
border-radius: 4px;
|
|
6075
|
+
cursor: pointer;
|
|
6076
|
+
flex-shrink: 0;
|
|
6077
|
+
transition: background-color var(--fb-transition-duration);
|
|
6078
|
+
`;
|
|
6079
|
+
rem.textContent = "\u2715";
|
|
6080
|
+
rem.addEventListener("mouseenter", () => {
|
|
6081
|
+
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
6082
|
+
});
|
|
6083
|
+
rem.addEventListener("mouseleave", () => {
|
|
6084
|
+
rem.style.backgroundColor = "transparent";
|
|
6085
|
+
});
|
|
6086
|
+
rem.onclick = onRemove;
|
|
6087
|
+
const labelRow = item.querySelector("[data-fb-label-row]");
|
|
6088
|
+
if (labelRow) {
|
|
6089
|
+
rem.style.marginLeft = "auto";
|
|
6090
|
+
labelRow.appendChild(rem);
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
rem.style.position = "absolute";
|
|
6094
|
+
rem.style.top = "8px";
|
|
6095
|
+
rem.style.right = "8px";
|
|
6096
|
+
item.style.position = "relative";
|
|
6097
|
+
item.appendChild(rem);
|
|
6098
|
+
}
|
|
5940
6099
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
5941
6100
|
const state = ctx.state;
|
|
5942
6101
|
const containerIsReadonly = isElementReadonly(element, state, ctx);
|
|
5943
6102
|
const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
|
|
5944
6103
|
const containerWrap = document.createElement("div");
|
|
5945
6104
|
containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
|
|
5946
|
-
const countDisplay = document.createElement("span");
|
|
5947
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
5948
6105
|
const itemsWrap = document.createElement("div");
|
|
5949
6106
|
const isSlides = element.displayMode === "slides";
|
|
5950
6107
|
if (isSlides) {
|
|
@@ -5966,92 +6123,64 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5966
6123
|
const pre = Array.isArray(ctx.prefill?.[element.key]) ? ctx.prefill[element.key] : null;
|
|
5967
6124
|
const childDefaults = extractChildDefaults(element.elements);
|
|
5968
6125
|
const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
|
|
5969
|
-
const
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
add.addEventListener("mouseenter", () => {
|
|
5982
|
-
add.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5983
|
-
});
|
|
5984
|
-
add.addEventListener("mouseleave", () => {
|
|
5985
|
-
add.style.backgroundColor = "transparent";
|
|
5986
|
-
});
|
|
5987
|
-
add.onclick = () => {
|
|
5988
|
-
if (countItems() < max) {
|
|
5989
|
-
const idx = countItems();
|
|
5990
|
-
const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
|
|
5991
|
-
const subCtx = {
|
|
5992
|
-
state: ctx.state,
|
|
5993
|
-
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
5994
|
-
prefill: childDefaults,
|
|
5995
|
-
// Defaults for enableIf evaluation
|
|
5996
|
-
formData: currentFormData,
|
|
5997
|
-
// Current root data from DOM for enableIf
|
|
5998
|
-
inheritedReadonly: childInheritedReadonly
|
|
5999
|
-
};
|
|
6000
|
-
const item = document.createElement("div");
|
|
6001
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6002
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6003
|
-
const childWrapper = document.createElement("div");
|
|
6004
|
-
childWrapper.className = getChildWrapperClass(isSlides, element.columns);
|
|
6005
|
-
element.elements.forEach((child) => {
|
|
6006
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6007
|
-
childWrapper.appendChild(
|
|
6008
|
-
createHiddenInput(
|
|
6009
|
-
pathJoin(subCtx.path, child.key),
|
|
6010
|
-
("default" in child ? child.default : null) ?? null
|
|
6011
|
-
)
|
|
6012
|
-
);
|
|
6013
|
-
} else {
|
|
6014
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
6015
|
-
}
|
|
6016
|
-
});
|
|
6017
|
-
item.appendChild(childWrapper);
|
|
6018
|
-
if (!containerIsReadonly) {
|
|
6019
|
-
const rem = document.createElement("button");
|
|
6020
|
-
rem.type = "button";
|
|
6021
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
6022
|
-
rem.style.cssText = `
|
|
6023
|
-
color: var(--fb-error-color);
|
|
6024
|
-
background-color: transparent;
|
|
6025
|
-
transition: background-color var(--fb-transition-duration);
|
|
6026
|
-
`;
|
|
6027
|
-
rem.textContent = "\u2715";
|
|
6028
|
-
rem.addEventListener("mouseenter", () => {
|
|
6029
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
6030
|
-
});
|
|
6031
|
-
rem.addEventListener("mouseleave", () => {
|
|
6032
|
-
rem.style.backgroundColor = "transparent";
|
|
6033
|
-
});
|
|
6034
|
-
rem.onclick = () => handleRemoveItem(item);
|
|
6035
|
-
item.style.position = "relative";
|
|
6036
|
-
item.appendChild(rem);
|
|
6037
|
-
}
|
|
6038
|
-
itemsWrap.appendChild(item);
|
|
6039
|
-
updateAddButton();
|
|
6040
|
-
}
|
|
6126
|
+
const handleAddItem = () => {
|
|
6127
|
+
if (countItems() >= max) return;
|
|
6128
|
+
const idx = countItems();
|
|
6129
|
+
const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
|
|
6130
|
+
const subCtx = {
|
|
6131
|
+
state: ctx.state,
|
|
6132
|
+
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6133
|
+
prefill: childDefaults,
|
|
6134
|
+
// Defaults for enableIf evaluation
|
|
6135
|
+
formData: currentFormData,
|
|
6136
|
+
// Current root data from DOM for enableIf
|
|
6137
|
+
inheritedReadonly: childInheritedReadonly
|
|
6041
6138
|
};
|
|
6042
|
-
|
|
6139
|
+
const item = document.createElement("div");
|
|
6140
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6141
|
+
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6142
|
+
const childWrapper = document.createElement("div");
|
|
6143
|
+
childWrapper.className = getChildWrapperClass(isSlides, element.columns);
|
|
6144
|
+
element.elements.forEach((child) => {
|
|
6145
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6146
|
+
childWrapper.appendChild(
|
|
6147
|
+
createHiddenInput(
|
|
6148
|
+
pathJoin(subCtx.path, child.key),
|
|
6149
|
+
("default" in child ? child.default : null) ?? null
|
|
6150
|
+
)
|
|
6151
|
+
);
|
|
6152
|
+
} else {
|
|
6153
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
6154
|
+
}
|
|
6155
|
+
});
|
|
6156
|
+
item.appendChild(childWrapper);
|
|
6157
|
+
if (!containerIsReadonly) {
|
|
6158
|
+
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
6159
|
+
}
|
|
6160
|
+
if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
|
|
6161
|
+
itemsWrap.insertBefore(item, slideAddTile);
|
|
6162
|
+
} else {
|
|
6163
|
+
itemsWrap.appendChild(item);
|
|
6164
|
+
}
|
|
6165
|
+
updateAddButton();
|
|
6043
6166
|
};
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6167
|
+
let slideAddTile = null;
|
|
6168
|
+
let slideAddUpdate = null;
|
|
6169
|
+
let pillAddUpdate = null;
|
|
6170
|
+
const syncSlideTileSize = () => {
|
|
6171
|
+
if (!slideAddTile) return;
|
|
6172
|
+
const firstSlide = itemsWrap.querySelector(
|
|
6173
|
+
":scope > .containerItem"
|
|
6048
6174
|
);
|
|
6049
|
-
if (
|
|
6050
|
-
|
|
6051
|
-
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
6052
|
-
existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
|
|
6175
|
+
if (firstSlide && firstSlide.offsetHeight > 0) {
|
|
6176
|
+
slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
|
|
6053
6177
|
}
|
|
6054
|
-
|
|
6178
|
+
};
|
|
6179
|
+
const updateAddButton = () => {
|
|
6180
|
+
const currentCount = countItems();
|
|
6181
|
+
if (slideAddUpdate) slideAddUpdate(currentCount, max);
|
|
6182
|
+
if (pillAddUpdate) pillAddUpdate(currentCount, max);
|
|
6183
|
+
if (slideAddTile) syncSlideTileSize();
|
|
6055
6184
|
};
|
|
6056
6185
|
const handleRemoveItem = (item) => {
|
|
6057
6186
|
item.remove();
|
|
@@ -6095,24 +6224,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6095
6224
|
});
|
|
6096
6225
|
item.appendChild(childWrapper);
|
|
6097
6226
|
if (!containerIsReadonly) {
|
|
6098
|
-
|
|
6099
|
-
rem.type = "button";
|
|
6100
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
6101
|
-
rem.style.cssText = `
|
|
6102
|
-
color: var(--fb-error-color);
|
|
6103
|
-
background-color: transparent;
|
|
6104
|
-
transition: background-color var(--fb-transition-duration);
|
|
6105
|
-
`;
|
|
6106
|
-
rem.textContent = "\u2715";
|
|
6107
|
-
rem.addEventListener("mouseenter", () => {
|
|
6108
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
6109
|
-
});
|
|
6110
|
-
rem.addEventListener("mouseleave", () => {
|
|
6111
|
-
rem.style.backgroundColor = "transparent";
|
|
6112
|
-
});
|
|
6113
|
-
rem.onclick = () => handleRemoveItem(item);
|
|
6114
|
-
item.style.position = "relative";
|
|
6115
|
-
item.appendChild(rem);
|
|
6227
|
+
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
6116
6228
|
}
|
|
6117
6229
|
itemsWrap.appendChild(item);
|
|
6118
6230
|
});
|
|
@@ -6156,41 +6268,43 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6156
6268
|
}
|
|
6157
6269
|
});
|
|
6158
6270
|
item.appendChild(childWrapper);
|
|
6159
|
-
|
|
6160
|
-
rem.type = "button";
|
|
6161
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
6162
|
-
rem.style.cssText = `
|
|
6163
|
-
color: var(--fb-error-color);
|
|
6164
|
-
background-color: transparent;
|
|
6165
|
-
transition: background-color var(--fb-transition-duration);
|
|
6166
|
-
`;
|
|
6167
|
-
rem.textContent = "\u2715";
|
|
6168
|
-
rem.addEventListener("mouseenter", () => {
|
|
6169
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
6170
|
-
});
|
|
6171
|
-
rem.addEventListener("mouseleave", () => {
|
|
6172
|
-
rem.style.backgroundColor = "transparent";
|
|
6173
|
-
});
|
|
6174
|
-
rem.onclick = () => {
|
|
6271
|
+
mountRemoveButton(item, () => {
|
|
6175
6272
|
if (countItems() > min) {
|
|
6176
6273
|
handleRemoveItem(item);
|
|
6177
6274
|
}
|
|
6178
|
-
};
|
|
6179
|
-
item.style.position = "relative";
|
|
6180
|
-
item.appendChild(rem);
|
|
6275
|
+
});
|
|
6181
6276
|
itemsWrap.appendChild(item);
|
|
6182
6277
|
}
|
|
6183
6278
|
}
|
|
6184
6279
|
containerWrap.appendChild(itemsWrap);
|
|
6185
6280
|
if (!containerIsReadonly) {
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6281
|
+
if (isSlides) {
|
|
6282
|
+
itemsWrap.style.alignItems = "stretch";
|
|
6283
|
+
const handle = createSlideAddTile(handleAddItem, {
|
|
6284
|
+
label: element.addLabel
|
|
6285
|
+
});
|
|
6286
|
+
slideAddTile = handle.tile;
|
|
6287
|
+
slideAddUpdate = handle.update;
|
|
6288
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
6289
|
+
itemsWrap.appendChild(handle.tile);
|
|
6290
|
+
} else {
|
|
6291
|
+
const handle = createAddItemRow("container", handleAddItem, {
|
|
6292
|
+
label: element.addLabel
|
|
6293
|
+
});
|
|
6294
|
+
pillAddUpdate = handle.update;
|
|
6295
|
+
mountCounterInLabel(wrapper, handle.counter);
|
|
6296
|
+
containerWrap.appendChild(handle.row);
|
|
6297
|
+
}
|
|
6191
6298
|
}
|
|
6192
6299
|
updateAddButton();
|
|
6193
6300
|
wrapper.appendChild(containerWrap);
|
|
6301
|
+
if (slideAddTile) {
|
|
6302
|
+
if (typeof requestAnimationFrame === "function") {
|
|
6303
|
+
requestAnimationFrame(syncSlideTileSize);
|
|
6304
|
+
} else {
|
|
6305
|
+
syncSlideTileSize();
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6194
6308
|
}
|
|
6195
6309
|
var validateElementFunc = null;
|
|
6196
6310
|
function setValidateElement(fn) {
|
|
@@ -9085,10 +9199,7 @@ var TAGS = {
|
|
|
9085
9199
|
"-": ["<hr />"]
|
|
9086
9200
|
};
|
|
9087
9201
|
function outdent(str) {
|
|
9088
|
-
return str.replace(
|
|
9089
|
-
RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
|
|
9090
|
-
""
|
|
9091
|
-
);
|
|
9202
|
+
return str.replace(RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"), "");
|
|
9092
9203
|
}
|
|
9093
9204
|
function encodeAttr(str) {
|
|
9094
9205
|
return (str + "").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -9251,12 +9362,7 @@ function ensureMarkdownStyles() {
|
|
|
9251
9362
|
`;
|
|
9252
9363
|
document.head.appendChild(style);
|
|
9253
9364
|
}
|
|
9254
|
-
var ANCHOR_DANGEROUS_SCHEMES = [
|
|
9255
|
-
"javascript:",
|
|
9256
|
-
"data:",
|
|
9257
|
-
"vbscript:",
|
|
9258
|
-
"blob:"
|
|
9259
|
-
];
|
|
9365
|
+
var ANCHOR_DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:", "blob:"];
|
|
9260
9366
|
var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
|
|
9261
9367
|
function isImgSrcDangerous(normalized) {
|
|
9262
9368
|
if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
|
|
@@ -9577,6 +9683,7 @@ function createInfoButton(element) {
|
|
|
9577
9683
|
function createLabelContainer(element) {
|
|
9578
9684
|
const label = document.createElement("div");
|
|
9579
9685
|
label.className = "flex items-center mb-1";
|
|
9686
|
+
label.dataset.fbLabelRow = "";
|
|
9580
9687
|
const title = createFieldLabel(element);
|
|
9581
9688
|
label.appendChild(title);
|
|
9582
9689
|
if (element.description || element.hint) {
|
|
@@ -9794,6 +9901,7 @@ var defaultConfig = {
|
|
|
9794
9901
|
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
9795
9902
|
invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
|
|
9796
9903
|
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
9904
|
+
uploadFailed: 'Failed to upload "{name}": {error}',
|
|
9797
9905
|
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
9798
9906
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
9799
9907
|
invalidOption: "Invalid option",
|
|
@@ -9869,6 +9977,7 @@ var defaultConfig = {
|
|
|
9869
9977
|
invalidFileExtension: '\u0424\u0430\u0439\u043B "{name}" \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0435: {formats}',
|
|
9870
9978
|
invalidFileMime: '\u0424\u0430\u0439\u043B "{name}": \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430 {type} \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D (\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u044B: {mimes})',
|
|
9871
9979
|
fileTooLarge: '\u0424\u0430\u0439\u043B "{name}" \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0435\u0442 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 {maxSize}\u041C\u0411',
|
|
9980
|
+
uploadFailed: '\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C "{name}": {error}',
|
|
9872
9981
|
filesLimitExceeded: "{skipped} \u0444\u0430\u0439\u043B(\u043E\u0432) \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9873
9982
|
unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}",
|
|
9874
9983
|
invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
|
|
@@ -10091,29 +10200,6 @@ var exampleThemes = {
|
|
|
10091
10200
|
}
|
|
10092
10201
|
};
|
|
10093
10202
|
|
|
10094
|
-
// src/utils/styles.ts
|
|
10095
|
-
function applyActionButtonStyles(button, isFormLevel = false) {
|
|
10096
|
-
button.style.cssText = `
|
|
10097
|
-
background-color: var(--fb-action-bg-color);
|
|
10098
|
-
color: var(--fb-action-text-color);
|
|
10099
|
-
border: var(--fb-border-width) solid var(--fb-action-border-color);
|
|
10100
|
-
padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
|
|
10101
|
-
font-size: var(--fb-font-size);
|
|
10102
|
-
font-weight: var(--fb-font-weight-medium);
|
|
10103
|
-
border-radius: var(--fb-border-radius);
|
|
10104
|
-
transition: all var(--fb-transition-duration);
|
|
10105
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
10106
|
-
`;
|
|
10107
|
-
button.addEventListener("mouseenter", () => {
|
|
10108
|
-
button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
|
|
10109
|
-
button.style.borderColor = "var(--fb-action-hover-border-color)";
|
|
10110
|
-
});
|
|
10111
|
-
button.addEventListener("mouseleave", () => {
|
|
10112
|
-
button.style.backgroundColor = "var(--fb-action-bg-color)";
|
|
10113
|
-
button.style.borderColor = "var(--fb-action-border-color)";
|
|
10114
|
-
});
|
|
10115
|
-
}
|
|
10116
|
-
|
|
10117
10203
|
// src/components/registry.ts
|
|
10118
10204
|
function validateHiddenElement(element, key, context) {
|
|
10119
10205
|
const { scopeRoot } = context;
|