@dmitryvim/form-builder 0.2.30 → 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 +577 -448
- package/dist/browser/formbuilder.v0.2.34.min.js +1162 -0
- package/dist/cjs/index.cjs +1644 -1174
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1617 -1158
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +577 -448
- package/dist/types/components/colour.d.ts +1 -0
- package/dist/types/components/file/dom.d.ts +4 -5
- package/dist/types/components/file/preview.d.ts +5 -0
- package/dist/types/components/file/render-edit.d.ts +8 -1
- package/dist/types/components/file/render-readonly.d.ts +4 -7
- package/dist/types/components/file/upload.d.ts +10 -0
- package/dist/types/components/file.d.ts +2 -2
- package/dist/types/types/config.d.ts +5 -0
- package/dist/types/types/schema.d.ts +16 -0
- package/dist/types/utils/styles.d.ts +42 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.30.min.js +0 -1033
package/dist/esm/index.js
CHANGED
|
@@ -46,8 +46,9 @@ function addRangeHint(element, parts, state) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
function addFileSizeHint(element, parts, state) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const sizeMB = element.maxSize ?? element.maxSizeMB;
|
|
50
|
+
if (sizeMB && sizeMB !== Infinity) {
|
|
51
|
+
parts.push(t("hintMaxSize", state, { size: sizeMB }));
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
function addFormatHint(element, parts, state) {
|
|
@@ -120,6 +121,25 @@ function validateSchema(schema) {
|
|
|
120
121
|
});
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
function validateContainerProps(element, elementPath, errors2) {
|
|
125
|
+
if ("columns" in element && element.columns !== void 0) {
|
|
126
|
+
const columns = element.columns;
|
|
127
|
+
const validColumns = [1, 2, 3, 4];
|
|
128
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
129
|
+
errors2.push(
|
|
130
|
+
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if ("displayMode" in element && element.displayMode !== void 0) {
|
|
135
|
+
const displayMode = element.displayMode;
|
|
136
|
+
if (displayMode !== "stack" && displayMode !== "slides") {
|
|
137
|
+
errors2.push(
|
|
138
|
+
`${elementPath}: displayMode must be "stack" or "slides" (got ${JSON.stringify(displayMode)})`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
123
143
|
function checkFlatOutputCollisions(elements, scopePath) {
|
|
124
144
|
const allOutputKeys = /* @__PURE__ */ new Set();
|
|
125
145
|
for (const el of elements) {
|
|
@@ -199,15 +219,7 @@ function validateSchema(schema) {
|
|
|
199
219
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
200
220
|
}
|
|
201
221
|
if (element.type === "container" && element.elements) {
|
|
202
|
-
|
|
203
|
-
const columns = element.columns;
|
|
204
|
-
const validColumns = [1, 2, 3, 4];
|
|
205
|
-
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
206
|
-
errors.push(
|
|
207
|
-
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
222
|
+
validateContainerProps(element, elementPath, errors);
|
|
211
223
|
if ("prefillHints" in element && element.prefillHints) {
|
|
212
224
|
const prefillHints = element.prefillHints;
|
|
213
225
|
if (Array.isArray(prefillHints)) {
|
|
@@ -386,6 +398,167 @@ function deepEqual(a, b) {
|
|
|
386
398
|
return a === b;
|
|
387
399
|
}
|
|
388
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
|
+
|
|
389
562
|
// src/components/text.ts
|
|
390
563
|
function createCharCounter(element, input, isTextarea = false) {
|
|
391
564
|
const counter = document.createElement("span");
|
|
@@ -438,12 +611,13 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
438
611
|
const readonly = isElementReadonly(element, state, ctx);
|
|
439
612
|
const inputWrapper = document.createElement("div");
|
|
440
613
|
inputWrapper.style.cssText = "position: relative;";
|
|
614
|
+
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
441
615
|
const textInput = document.createElement("input");
|
|
442
616
|
textInput.type = "text";
|
|
443
617
|
textInput.className = "w-full rounded-lg";
|
|
444
618
|
textInput.style.cssText = `
|
|
445
619
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
446
|
-
padding-right: 60px;
|
|
620
|
+
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
447
621
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
448
622
|
border-radius: var(--fb-border-radius);
|
|
449
623
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -488,7 +662,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
488
662
|
textInput.addEventListener("input", handleChange);
|
|
489
663
|
}
|
|
490
664
|
inputWrapper.appendChild(textInput);
|
|
491
|
-
if (
|
|
665
|
+
if (hasCharCounter) {
|
|
492
666
|
const counter = createCharCounter(element, textInput, false);
|
|
493
667
|
inputWrapper.appendChild(counter);
|
|
494
668
|
}
|
|
@@ -499,6 +673,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
499
673
|
const readonly = isElementReadonly(element, state, ctx);
|
|
500
674
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
501
675
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
676
|
+
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
502
677
|
const minCount = element.minCount ?? 1;
|
|
503
678
|
const maxCount = element.maxCount ?? Infinity;
|
|
504
679
|
while (values.length < minCount) {
|
|
@@ -525,7 +700,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
525
700
|
textInput.type = "text";
|
|
526
701
|
textInput.style.cssText = `
|
|
527
702
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
528
|
-
padding-right: 60px;
|
|
703
|
+
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
529
704
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
530
705
|
border-radius: var(--fb-border-radius);
|
|
531
706
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -569,7 +744,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
569
744
|
textInput.addEventListener("input", handleChange);
|
|
570
745
|
}
|
|
571
746
|
inputContainer.appendChild(textInput);
|
|
572
|
-
if (
|
|
747
|
+
if (hasCharCounter) {
|
|
573
748
|
const counter = createCharCounter(element, textInput, false);
|
|
574
749
|
inputContainer.appendChild(counter);
|
|
575
750
|
}
|
|
@@ -626,50 +801,24 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
626
801
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
627
802
|
});
|
|
628
803
|
}
|
|
629
|
-
let
|
|
630
|
-
let countDisplay = null;
|
|
804
|
+
let addUpdate = null;
|
|
631
805
|
if (!readonly) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
646
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
647
|
-
});
|
|
648
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
649
|
-
addBtn.style.backgroundColor = "transparent";
|
|
650
|
-
});
|
|
651
|
-
addBtn.onclick = () => {
|
|
652
|
-
values.push(element.default || "");
|
|
653
|
-
addTextItem(element.default || "");
|
|
654
|
-
updateAddButton();
|
|
655
|
-
updateRemoveButtons();
|
|
656
|
-
};
|
|
657
|
-
countDisplay = document.createElement("span");
|
|
658
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
659
|
-
addRow.appendChild(addBtn);
|
|
660
|
-
addRow.appendChild(countDisplay);
|
|
661
|
-
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);
|
|
662
819
|
}
|
|
663
820
|
function updateAddButton() {
|
|
664
|
-
if (
|
|
665
|
-
const addBtn = addRow.querySelector(".add-text-btn");
|
|
666
|
-
if (addBtn) {
|
|
667
|
-
const disabled = values.length >= maxCount;
|
|
668
|
-
addBtn.disabled = disabled;
|
|
669
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
670
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
671
|
-
}
|
|
672
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
821
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
673
822
|
}
|
|
674
823
|
values.forEach((value) => addTextItem(value));
|
|
675
824
|
updateAddButton();
|
|
@@ -840,8 +989,12 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
840
989
|
const textareaWrapper = document.createElement("div");
|
|
841
990
|
textareaWrapper.style.cssText = "position: relative;";
|
|
842
991
|
const textareaInput = document.createElement("textarea");
|
|
843
|
-
textareaInput.className = "w-full
|
|
844
|
-
textareaInput.style.cssText =
|
|
992
|
+
textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
993
|
+
textareaInput.style.cssText = `
|
|
994
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
|
|
995
|
+
font-size: var(--fb-font-size);
|
|
996
|
+
font-family: var(--fb-font-family);
|
|
997
|
+
`;
|
|
845
998
|
textareaInput.name = pathKey;
|
|
846
999
|
textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
847
1000
|
textareaInput.rows = element.rows || 4;
|
|
@@ -893,8 +1046,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
893
1046
|
const textareaContainer = document.createElement("div");
|
|
894
1047
|
textareaContainer.style.cssText = "position: relative;";
|
|
895
1048
|
const textareaInput = document.createElement("textarea");
|
|
896
|
-
textareaInput.className = "w-full
|
|
897
|
-
textareaInput.style.cssText =
|
|
1049
|
+
textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
1050
|
+
textareaInput.style.cssText = `
|
|
1051
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
|
|
1052
|
+
font-size: var(--fb-font-size);
|
|
1053
|
+
font-family: var(--fb-font-family);
|
|
1054
|
+
`;
|
|
898
1055
|
textareaInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
899
1056
|
textareaInput.rows = element.rows || 4;
|
|
900
1057
|
textareaInput.value = value;
|
|
@@ -957,52 +1114,24 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
957
1114
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
958
1115
|
});
|
|
959
1116
|
}
|
|
960
|
-
let
|
|
961
|
-
let countDisplay = null;
|
|
1117
|
+
let addUpdate = null;
|
|
962
1118
|
if (!readonly) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
977
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
978
|
-
});
|
|
979
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
980
|
-
addBtn.style.backgroundColor = "transparent";
|
|
981
|
-
});
|
|
982
|
-
addBtn.onclick = () => {
|
|
983
|
-
values.push(element.default || "");
|
|
984
|
-
addTextareaItem(element.default || "");
|
|
985
|
-
updateAddButton();
|
|
986
|
-
updateRemoveButtons();
|
|
987
|
-
};
|
|
988
|
-
countDisplay = document.createElement("span");
|
|
989
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
990
|
-
addRow.appendChild(addBtn);
|
|
991
|
-
addRow.appendChild(countDisplay);
|
|
992
|
-
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);
|
|
993
1132
|
}
|
|
994
1133
|
function updateAddButton() {
|
|
995
|
-
if (
|
|
996
|
-
const addBtn = addRow.querySelector(
|
|
997
|
-
".add-textarea-btn"
|
|
998
|
-
);
|
|
999
|
-
if (addBtn) {
|
|
1000
|
-
const disabled = values.length >= maxCount;
|
|
1001
|
-
addBtn.disabled = disabled;
|
|
1002
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1003
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1004
|
-
}
|
|
1005
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1134
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1006
1135
|
}
|
|
1007
1136
|
values.forEach((value) => addTextareaItem(value));
|
|
1008
1137
|
updateAddButton();
|
|
@@ -1080,8 +1209,14 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1080
1209
|
inputWrapper.style.cssText = "position: relative;";
|
|
1081
1210
|
const numberInput = document.createElement("input");
|
|
1082
1211
|
numberInput.type = "number";
|
|
1083
|
-
numberInput.className = "w-full
|
|
1084
|
-
numberInput.style.cssText =
|
|
1212
|
+
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1213
|
+
numberInput.style.cssText = `
|
|
1214
|
+
padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1215
|
+
font-size: var(--fb-font-size);
|
|
1216
|
+
font-family: var(--fb-font-family);
|
|
1217
|
+
width: 100%;
|
|
1218
|
+
box-sizing: border-box;
|
|
1219
|
+
`;
|
|
1085
1220
|
numberInput.name = pathKey;
|
|
1086
1221
|
numberInput.placeholder = element.placeholder || "0";
|
|
1087
1222
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
@@ -1133,8 +1268,14 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1133
1268
|
inputContainer.style.cssText = "position: relative; flex: 1;";
|
|
1134
1269
|
const numberInput = document.createElement("input");
|
|
1135
1270
|
numberInput.type = "number";
|
|
1136
|
-
numberInput.className = "w-full
|
|
1137
|
-
numberInput.style.cssText =
|
|
1271
|
+
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1272
|
+
numberInput.style.cssText = `
|
|
1273
|
+
padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1274
|
+
font-size: var(--fb-font-size);
|
|
1275
|
+
font-family: var(--fb-font-family);
|
|
1276
|
+
width: 100%;
|
|
1277
|
+
box-sizing: border-box;
|
|
1278
|
+
`;
|
|
1138
1279
|
numberInput.placeholder = element.placeholder || "0";
|
|
1139
1280
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
1140
1281
|
if (element.max !== void 0) numberInput.max = element.max.toString();
|
|
@@ -1196,50 +1337,24 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1196
1337
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1197
1338
|
});
|
|
1198
1339
|
}
|
|
1199
|
-
let
|
|
1200
|
-
let countDisplay = null;
|
|
1340
|
+
let addUpdate = null;
|
|
1201
1341
|
if (!readonly) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
1216
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1217
|
-
});
|
|
1218
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1219
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1220
|
-
});
|
|
1221
|
-
addBtn.onclick = () => {
|
|
1222
|
-
values.push(element.default || "");
|
|
1223
|
-
addNumberItem(element.default || "");
|
|
1224
|
-
updateAddButton();
|
|
1225
|
-
updateRemoveButtons();
|
|
1226
|
-
};
|
|
1227
|
-
countDisplay = document.createElement("span");
|
|
1228
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1229
|
-
addRow.appendChild(addBtn);
|
|
1230
|
-
addRow.appendChild(countDisplay);
|
|
1231
|
-
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);
|
|
1232
1355
|
}
|
|
1233
1356
|
function updateAddButton() {
|
|
1234
|
-
if (
|
|
1235
|
-
const addBtn = addRow.querySelector(".add-number-btn");
|
|
1236
|
-
if (addBtn) {
|
|
1237
|
-
const disabled = values.length >= maxCount;
|
|
1238
|
-
addBtn.disabled = disabled;
|
|
1239
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1240
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1241
|
-
}
|
|
1242
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1357
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1243
1358
|
}
|
|
1244
1359
|
values.forEach((value) => addNumberItem(value));
|
|
1245
1360
|
updateAddButton();
|
|
@@ -1405,7 +1520,12 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1405
1520
|
const state = ctx.state;
|
|
1406
1521
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1407
1522
|
const selectInput = document.createElement("select");
|
|
1408
|
-
selectInput.className = "w-full
|
|
1523
|
+
selectInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1524
|
+
selectInput.style.cssText = `
|
|
1525
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1526
|
+
font-size: var(--fb-font-size);
|
|
1527
|
+
font-family: var(--fb-font-family);
|
|
1528
|
+
`;
|
|
1409
1529
|
selectInput.name = pathKey;
|
|
1410
1530
|
selectInput.disabled = readonly;
|
|
1411
1531
|
(element.options || []).forEach((option) => {
|
|
@@ -1457,7 +1577,12 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1457
1577
|
const itemWrapper = document.createElement("div");
|
|
1458
1578
|
itemWrapper.className = "multiple-select-item flex items-center gap-2";
|
|
1459
1579
|
const selectInput = document.createElement("select");
|
|
1460
|
-
selectInput.className = "flex-1
|
|
1580
|
+
selectInput.className = "flex-1 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1581
|
+
selectInput.style.cssText = `
|
|
1582
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1583
|
+
font-size: var(--fb-font-size);
|
|
1584
|
+
font-family: var(--fb-font-family);
|
|
1585
|
+
`;
|
|
1461
1586
|
selectInput.disabled = readonly;
|
|
1462
1587
|
(element.options || []).forEach((option) => {
|
|
1463
1588
|
const optionElement = document.createElement("option");
|
|
@@ -1514,51 +1639,25 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1514
1639
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1515
1640
|
});
|
|
1516
1641
|
}
|
|
1517
|
-
let
|
|
1518
|
-
let countDisplay = null;
|
|
1642
|
+
let addUpdate = null;
|
|
1519
1643
|
if (!readonly) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1535
|
-
});
|
|
1536
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1537
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1538
|
-
});
|
|
1539
|
-
addBtn.onclick = () => {
|
|
1540
|
-
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1541
|
-
values.push(defaultValue);
|
|
1542
|
-
addSelectItem(defaultValue);
|
|
1543
|
-
updateAddButton();
|
|
1544
|
-
updateRemoveButtons();
|
|
1545
|
-
};
|
|
1546
|
-
countDisplay = document.createElement("span");
|
|
1547
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1548
|
-
addRow.appendChild(addBtn);
|
|
1549
|
-
addRow.appendChild(countDisplay);
|
|
1550
|
-
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);
|
|
1551
1658
|
}
|
|
1552
1659
|
function updateAddButton() {
|
|
1553
|
-
if (
|
|
1554
|
-
const addBtn = addRow.querySelector(".add-select-btn");
|
|
1555
|
-
if (addBtn) {
|
|
1556
|
-
const disabled = values.length >= maxCount;
|
|
1557
|
-
addBtn.disabled = disabled;
|
|
1558
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1559
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1560
|
-
}
|
|
1561
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1660
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1562
1661
|
}
|
|
1563
1662
|
values.forEach((value) => addSelectItem(value));
|
|
1564
1663
|
updateAddButton();
|
|
@@ -1901,53 +2000,25 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
1901
2000
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1902
2001
|
});
|
|
1903
2002
|
}
|
|
1904
|
-
let
|
|
1905
|
-
let countDisplay = null;
|
|
2003
|
+
let addUpdate = null;
|
|
1906
2004
|
if (!readonly) {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1922
|
-
});
|
|
1923
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
1924
|
-
addBtn.style.backgroundColor = "transparent";
|
|
1925
|
-
});
|
|
1926
|
-
addBtn.onclick = () => {
|
|
1927
|
-
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1928
|
-
values.push(defaultValue);
|
|
1929
|
-
addSwitcherItem(defaultValue);
|
|
1930
|
-
updateAddButton();
|
|
1931
|
-
updateRemoveButtons();
|
|
1932
|
-
};
|
|
1933
|
-
countDisplay = document.createElement("span");
|
|
1934
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
1935
|
-
addRow.appendChild(addBtn);
|
|
1936
|
-
addRow.appendChild(countDisplay);
|
|
1937
|
-
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);
|
|
1938
2019
|
}
|
|
1939
2020
|
function updateAddButton() {
|
|
1940
|
-
if (
|
|
1941
|
-
const addBtn = addRow.querySelector(
|
|
1942
|
-
".add-switcher-btn"
|
|
1943
|
-
);
|
|
1944
|
-
if (addBtn) {
|
|
1945
|
-
const disabled = values.length >= maxCount;
|
|
1946
|
-
addBtn.disabled = disabled;
|
|
1947
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1948
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1949
|
-
}
|
|
1950
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
2021
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
1951
2022
|
}
|
|
1952
2023
|
values.forEach((value) => addSwitcherItem(value));
|
|
1953
2024
|
updateAddButton();
|
|
@@ -2192,7 +2263,13 @@ function ensureFileStyles() {
|
|
|
2192
2263
|
style.textContent = `
|
|
2193
2264
|
@keyframes fb-spin { to { transform: rotate(360deg); } }
|
|
2194
2265
|
|
|
2195
|
-
/*
|
|
2266
|
+
/* \u2500\u2500\u2500 Checker background utility \u2500\u2500\u2500 */
|
|
2267
|
+
/* Neutral diagonal-stripe background for image previews (never crops) */
|
|
2268
|
+
.fb-checker {
|
|
2269
|
+
background-image: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/* \u2500\u2500\u2500 Spinner \u2500\u2500\u2500 */
|
|
2196
2273
|
.fb-spinner {
|
|
2197
2274
|
width: 36px;
|
|
2198
2275
|
height: 36px;
|
|
@@ -2203,207 +2280,271 @@ function ensureFileStyles() {
|
|
|
2203
2280
|
flex-shrink: 0;
|
|
2204
2281
|
}
|
|
2205
2282
|
|
|
2206
|
-
/*
|
|
2207
|
-
.fb-tile {
|
|
2208
|
-
width:
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2283
|
+
/* \u2500\u2500\u2500 Wide single-file add tile (empty state) \u2500\u2500\u2500 */
|
|
2284
|
+
.fb-wide-tile {
|
|
2285
|
+
width: 100%;
|
|
2286
|
+
border-radius: 0.75rem;
|
|
2287
|
+
border: 1px dashed #60a5fa;
|
|
2288
|
+
background: rgba(239,246,255,0.5);
|
|
2289
|
+
display: flex;
|
|
2212
2290
|
overflow: hidden;
|
|
2213
|
-
|
|
2214
|
-
|
|
2291
|
+
height: 180px;
|
|
2292
|
+
transition: border-color 150ms, background 150ms, box-shadow 150ms;
|
|
2293
|
+
cursor: pointer;
|
|
2215
2294
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
.fb-tile-resource {
|
|
2219
|
-
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2295
|
+
.fb-wide-tile:hover {
|
|
2296
|
+
background: #eff6ff;
|
|
2220
2297
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2298
|
+
.fb-wide-tile.fb-drag-over {
|
|
2299
|
+
border-color: #3b82f6;
|
|
2300
|
+
border-width: 2px;
|
|
2301
|
+
background: #eff6ff;
|
|
2302
|
+
box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
|
|
2225
2303
|
}
|
|
2226
2304
|
|
|
2227
|
-
/*
|
|
2228
|
-
.fb-tile-
|
|
2229
|
-
|
|
2305
|
+
/* Upload zone inside wide tile */
|
|
2306
|
+
.fb-wide-tile-upload {
|
|
2307
|
+
flex: 1;
|
|
2230
2308
|
display: flex;
|
|
2309
|
+
flex-direction: column;
|
|
2231
2310
|
align-items: center;
|
|
2232
2311
|
justify-content: center;
|
|
2312
|
+
gap: 8px;
|
|
2313
|
+
color: #2563eb;
|
|
2314
|
+
padding: 16px;
|
|
2315
|
+
transition: background 150ms;
|
|
2233
2316
|
cursor: pointer;
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
border-color var(--fb-transition-duration, 200ms),
|
|
2238
|
-
color var(--fb-transition-duration, 200ms);
|
|
2317
|
+
background: transparent;
|
|
2318
|
+
border: none;
|
|
2319
|
+
font-family: inherit;
|
|
2239
2320
|
}
|
|
2240
|
-
.fb-tile-
|
|
2241
|
-
|
|
2242
|
-
color: var(--fb-text-color, #1f2937);
|
|
2321
|
+
.fb-wide-tile-upload:hover {
|
|
2322
|
+
background: rgba(191,219,254,0.25);
|
|
2243
2323
|
}
|
|
2244
2324
|
|
|
2245
|
-
/*
|
|
2246
|
-
.fb-tile-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
padding: 2px 6px;
|
|
2253
|
-
align-self: flex-end;
|
|
2254
|
-
margin-bottom: 4px;
|
|
2325
|
+
/* Vertical dashed divider between upload and library zones */
|
|
2326
|
+
.fb-wide-tile-divider {
|
|
2327
|
+
width: 1px;
|
|
2328
|
+
margin: 16px 0;
|
|
2329
|
+
border-left: 1px dashed rgba(96,165,250,0.5);
|
|
2330
|
+
background: transparent;
|
|
2331
|
+
flex-shrink: 0;
|
|
2255
2332
|
}
|
|
2256
2333
|
|
|
2257
|
-
/*
|
|
2258
|
-
.fb-
|
|
2259
|
-
width:
|
|
2260
|
-
|
|
2261
|
-
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2262
|
-
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2334
|
+
/* Library zone inside wide tile */
|
|
2335
|
+
.fb-wide-tile-library {
|
|
2336
|
+
width: 176px;
|
|
2337
|
+
flex-shrink: 0;
|
|
2263
2338
|
display: flex;
|
|
2264
2339
|
flex-direction: column;
|
|
2265
2340
|
align-items: center;
|
|
2266
2341
|
justify-content: center;
|
|
2267
|
-
gap:
|
|
2342
|
+
gap: 8px;
|
|
2343
|
+
color: #2563eb;
|
|
2344
|
+
padding: 12px;
|
|
2345
|
+
transition: background 150ms;
|
|
2268
2346
|
cursor: pointer;
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2347
|
+
background: transparent;
|
|
2348
|
+
border: none;
|
|
2349
|
+
font-family: inherit;
|
|
2272
2350
|
}
|
|
2273
|
-
.fb-
|
|
2274
|
-
|
|
2275
|
-
background: var(--fb-background-hover-color, #f9fafb);
|
|
2351
|
+
.fb-wide-tile-library:hover {
|
|
2352
|
+
background: rgba(191,219,254,0.25);
|
|
2276
2353
|
}
|
|
2277
2354
|
|
|
2278
|
-
/*
|
|
2279
|
-
.fb-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2355
|
+
/* \u2500\u2500\u2500 Multi-file outer grid container \u2500\u2500\u2500 */
|
|
2356
|
+
.fb-multi-outer {
|
|
2357
|
+
border-radius: 0.75rem;
|
|
2358
|
+
border: 1px dashed #cbd5e1;
|
|
2359
|
+
background: rgba(248,250,252,0.4);
|
|
2360
|
+
padding: 12px;
|
|
2361
|
+
transition: border-color 150ms, background 150ms, box-shadow 150ms;
|
|
2362
|
+
}
|
|
2363
|
+
.fb-multi-outer.fb-drag-over {
|
|
2364
|
+
border-width: 2px;
|
|
2365
|
+
border-color: #3b82f6;
|
|
2366
|
+
background: rgba(239,246,255,0.4);
|
|
2367
|
+
box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
|
|
2286
2368
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2369
|
+
|
|
2370
|
+
/* With files present: white solid border */
|
|
2371
|
+
.fb-multi-outer.fb-multi-has-files {
|
|
2372
|
+
border-style: solid;
|
|
2373
|
+
border-color: #e2e8f0;
|
|
2374
|
+
background: #fff;
|
|
2290
2375
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2376
|
+
|
|
2377
|
+
/* The CSS grid inside */
|
|
2378
|
+
.fb-multi-grid {
|
|
2379
|
+
display: grid;
|
|
2380
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
2381
|
+
gap: 10px;
|
|
2295
2382
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2383
|
+
|
|
2384
|
+
/* \u2500\u2500\u2500 Multi square add-tile (combined upload + library) \u2500\u2500\u2500 */
|
|
2385
|
+
.fb-multi-add-tile {
|
|
2386
|
+
aspect-ratio: 1 / 1;
|
|
2387
|
+
border-radius: 0.5rem;
|
|
2388
|
+
border: 1px dashed #60a5fa;
|
|
2389
|
+
background: rgba(239,246,255,0.5);
|
|
2390
|
+
display: flex;
|
|
2391
|
+
flex-direction: column;
|
|
2392
|
+
overflow: hidden;
|
|
2393
|
+
transition: background 150ms;
|
|
2300
2394
|
}
|
|
2301
|
-
.fb-
|
|
2302
|
-
|
|
2303
|
-
color: var(--fb-text-secondary-color, #6b7280);
|
|
2395
|
+
.fb-multi-add-tile:hover {
|
|
2396
|
+
background: #eff6ff;
|
|
2304
2397
|
}
|
|
2305
|
-
.fb-
|
|
2306
|
-
|
|
2307
|
-
color:
|
|
2398
|
+
.fb-multi-add-tile.fb-drag-over-tile {
|
|
2399
|
+
border-width: 2px;
|
|
2400
|
+
border-color: #3b82f6;
|
|
2401
|
+
background: rgba(255,255,255,0.8);
|
|
2308
2402
|
}
|
|
2309
2403
|
|
|
2310
|
-
/*
|
|
2311
|
-
.fb-
|
|
2312
|
-
|
|
2313
|
-
inset: 0;
|
|
2314
|
-
background: transparent;
|
|
2315
|
-
transition: background var(--fb-transition-duration, 200ms);
|
|
2316
|
-
display: flex;
|
|
2317
|
-
align-items: flex-start;
|
|
2318
|
-
justify-content: flex-end;
|
|
2319
|
-
}
|
|
2320
|
-
.fb-tile-resource:hover .fb-tile-overlay {
|
|
2321
|
-
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.4));
|
|
2322
|
-
}
|
|
2323
|
-
.fb-tile-x-btn {
|
|
2324
|
-
margin: 3px;
|
|
2325
|
-
width: 18px;
|
|
2326
|
-
height: 18px;
|
|
2327
|
-
background: var(--fb-error-color, #ef4444);
|
|
2328
|
-
color: var(--fb-file-bg-color, #fff);
|
|
2329
|
-
border: none;
|
|
2330
|
-
border-radius: 50%;
|
|
2331
|
-
font-size: 11px;
|
|
2332
|
-
line-height: 1;
|
|
2333
|
-
cursor: pointer;
|
|
2404
|
+
/* Upload half of add-tile */
|
|
2405
|
+
.fb-multi-add-upload {
|
|
2406
|
+
flex: 1;
|
|
2334
2407
|
display: flex;
|
|
2408
|
+
flex-direction: column;
|
|
2335
2409
|
align-items: center;
|
|
2336
2410
|
justify-content: center;
|
|
2337
|
-
|
|
2338
|
-
|
|
2411
|
+
gap: 4px;
|
|
2412
|
+
color: #2563eb;
|
|
2413
|
+
cursor: pointer;
|
|
2414
|
+
background: transparent;
|
|
2415
|
+
border: none;
|
|
2416
|
+
font-family: inherit;
|
|
2417
|
+
width: 100%;
|
|
2418
|
+
transition: background 150ms;
|
|
2339
2419
|
}
|
|
2340
|
-
.fb-
|
|
2341
|
-
|
|
2420
|
+
.fb-multi-add-upload:hover {
|
|
2421
|
+
background: rgba(191,219,254,0.35);
|
|
2342
2422
|
}
|
|
2343
2423
|
|
|
2344
|
-
/*
|
|
2345
|
-
.fb-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
align-items: center;
|
|
2350
|
-
justify-content: center;
|
|
2351
|
-
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.25));
|
|
2424
|
+
/* Horizontal dashed divider inside add-tile */
|
|
2425
|
+
.fb-multi-add-divider {
|
|
2426
|
+
border-top: 1px dashed rgba(96,165,250,0.5);
|
|
2427
|
+
margin: 0;
|
|
2428
|
+
flex-shrink: 0;
|
|
2352
2429
|
}
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2430
|
+
|
|
2431
|
+
/* Library strip at bottom of add-tile */
|
|
2432
|
+
.fb-multi-add-library {
|
|
2433
|
+
padding: 6px 0;
|
|
2356
2434
|
display: flex;
|
|
2357
2435
|
align-items: center;
|
|
2358
2436
|
justify-content: center;
|
|
2437
|
+
gap: 4px;
|
|
2438
|
+
color: #2563eb;
|
|
2439
|
+
font-size: 11px;
|
|
2440
|
+
font-weight: 500;
|
|
2441
|
+
cursor: pointer;
|
|
2442
|
+
background: transparent;
|
|
2443
|
+
border: none;
|
|
2444
|
+
font-family: inherit;
|
|
2445
|
+
width: 100%;
|
|
2446
|
+
transition: background 150ms;
|
|
2447
|
+
flex-shrink: 0;
|
|
2448
|
+
}
|
|
2449
|
+
.fb-multi-add-library:hover {
|
|
2450
|
+
background: rgba(191,219,254,0.35);
|
|
2359
2451
|
}
|
|
2360
2452
|
|
|
2361
|
-
/*
|
|
2362
|
-
.fb-
|
|
2453
|
+
/* \u2500\u2500\u2500 Capacity placeholder squares \u2500\u2500\u2500 */
|
|
2454
|
+
.fb-multi-placeholder {
|
|
2455
|
+
aspect-ratio: 1 / 1;
|
|
2456
|
+
border-radius: 0.5rem;
|
|
2457
|
+
border: 1px solid #e2e8f0;
|
|
2458
|
+
}
|
|
2459
|
+
.fb-multi-placeholder.fb-drag-over {
|
|
2460
|
+
border-width: 2px;
|
|
2461
|
+
border-style: dashed;
|
|
2462
|
+
border-color: #93c5fd;
|
|
2463
|
+
background: rgba(219,234,254,0.6);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
/* \u2500\u2500\u2500 Filled preview tile \u2500\u2500\u2500 */
|
|
2467
|
+
.fb-preview-tile {
|
|
2468
|
+
aspect-ratio: 1 / 1;
|
|
2469
|
+
border-radius: 0.5rem;
|
|
2470
|
+
border: 1px solid #e2e8f0;
|
|
2471
|
+
overflow: hidden;
|
|
2363
2472
|
position: relative;
|
|
2473
|
+
cursor: pointer;
|
|
2474
|
+
}
|
|
2475
|
+
.fb-preview-tile img {
|
|
2364
2476
|
width: 100%;
|
|
2365
2477
|
height: 100%;
|
|
2478
|
+
object-fit: contain;
|
|
2479
|
+
display: block;
|
|
2366
2480
|
}
|
|
2367
2481
|
|
|
2368
|
-
/*
|
|
2369
|
-
.fb-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
z-index: 10;
|
|
2482
|
+
/* \u2500\u2500\u2500 Uploading placeholder tile \u2500\u2500\u2500 */
|
|
2483
|
+
.fb-uploading-tile {
|
|
2484
|
+
aspect-ratio: 1 / 1;
|
|
2485
|
+
border-radius: 0.5rem;
|
|
2486
|
+
border: 2px dashed #d1d5db;
|
|
2374
2487
|
display: flex;
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2488
|
+
flex-direction: column;
|
|
2489
|
+
align-items: center;
|
|
2490
|
+
justify-content: center;
|
|
2491
|
+
gap: 6px;
|
|
2492
|
+
padding: 6px;
|
|
2379
2493
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2494
|
+
|
|
2495
|
+
/* \u2500\u2500\u2500 Meta line below multi grid \u2500\u2500\u2500 */
|
|
2496
|
+
.fb-meta-line {
|
|
2497
|
+
margin-top: 10px;
|
|
2498
|
+
display: flex;
|
|
2499
|
+
align-items: center;
|
|
2500
|
+
justify-content: space-between;
|
|
2501
|
+
gap: 8px;
|
|
2502
|
+
flex-wrap: wrap;
|
|
2383
2503
|
}
|
|
2384
|
-
.fb-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
line-height: 1.2;
|
|
2504
|
+
.fb-meta-text {
|
|
2505
|
+
font-size: 12px;
|
|
2506
|
+
color: #94a3b8;
|
|
2507
|
+
display: flex;
|
|
2508
|
+
align-items: center;
|
|
2509
|
+
gap: 8px;
|
|
2510
|
+
flex-wrap: wrap;
|
|
2392
2511
|
}
|
|
2393
|
-
.fb-
|
|
2394
|
-
|
|
2512
|
+
.fb-meta-dot {
|
|
2513
|
+
width: 4px;
|
|
2514
|
+
height: 4px;
|
|
2515
|
+
border-radius: 50%;
|
|
2516
|
+
background: #cbd5e1;
|
|
2517
|
+
flex-shrink: 0;
|
|
2395
2518
|
}
|
|
2396
|
-
.fb-
|
|
2397
|
-
|
|
2519
|
+
.fb-meta-mono {
|
|
2520
|
+
font-family: ui-monospace, 'JetBrains Mono', monospace;
|
|
2521
|
+
font-size: 11px;
|
|
2522
|
+
letter-spacing: -0.02em;
|
|
2523
|
+
}
|
|
2524
|
+
.fb-clear-all-btn {
|
|
2525
|
+
font-size: 12px;
|
|
2526
|
+
color: #94a3b8;
|
|
2527
|
+
background: none;
|
|
2528
|
+
border: none;
|
|
2529
|
+
cursor: pointer;
|
|
2530
|
+
padding: 0;
|
|
2531
|
+
font-family: inherit;
|
|
2532
|
+
transition: color 150ms;
|
|
2533
|
+
white-space: nowrap;
|
|
2534
|
+
flex-shrink: 0;
|
|
2398
2535
|
}
|
|
2399
|
-
.fb-
|
|
2400
|
-
|
|
2536
|
+
.fb-clear-all-btn:hover {
|
|
2537
|
+
color: #dc2626;
|
|
2401
2538
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2539
|
+
|
|
2540
|
+
/* \u2500\u2500\u2500 Empty text (readonly) \u2500\u2500\u2500 */
|
|
2541
|
+
.fb-tile-empty-text {
|
|
2542
|
+
font-size: 11px;
|
|
2543
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2544
|
+
padding: 4px 0;
|
|
2404
2545
|
}
|
|
2405
2546
|
|
|
2406
|
-
/* Tile action
|
|
2547
|
+
/* \u2500\u2500\u2500 Tile action buttons (for zoom popup, compat) \u2500\u2500\u2500 */
|
|
2407
2548
|
.fb-tile-actions {
|
|
2408
2549
|
position: absolute;
|
|
2409
2550
|
top: 3px;
|
|
@@ -2415,37 +2556,35 @@ function ensureFileStyles() {
|
|
|
2415
2556
|
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2416
2557
|
z-index: 10;
|
|
2417
2558
|
}
|
|
2418
|
-
.fb-tile
|
|
2559
|
+
.fb-preview-tile:hover .fb-tile-actions {
|
|
2419
2560
|
opacity: 1;
|
|
2420
2561
|
}
|
|
2421
2562
|
.fb-tile-action-btn {
|
|
2422
|
-
width:
|
|
2423
|
-
height:
|
|
2563
|
+
width: 24px;
|
|
2564
|
+
height: 24px;
|
|
2424
2565
|
display: flex;
|
|
2425
2566
|
align-items: center;
|
|
2426
2567
|
justify-content: center;
|
|
2427
|
-
border:
|
|
2428
|
-
border-radius:
|
|
2568
|
+
border: 1px solid rgba(15,23,42,0.08);
|
|
2569
|
+
border-radius: 0.375rem;
|
|
2429
2570
|
cursor: pointer;
|
|
2430
|
-
background: rgba(
|
|
2431
|
-
color: #
|
|
2571
|
+
background: rgba(255,255,255,0.92);
|
|
2572
|
+
color: #374151;
|
|
2432
2573
|
padding: 0;
|
|
2433
2574
|
flex-shrink: 0;
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2575
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
2576
|
+
transition: background var(--fb-transition-duration, 200ms),
|
|
2577
|
+
color var(--fb-transition-duration, 200ms);
|
|
2437
2578
|
}
|
|
2438
2579
|
.fb-tile-action-btn:hover {
|
|
2439
|
-
background:
|
|
2440
|
-
|
|
2441
|
-
.fb-tile-action-remove {
|
|
2442
|
-
background: rgba(220, 38, 38, 0.8);
|
|
2580
|
+
background: #ffffff;
|
|
2581
|
+
color: #0f172a;
|
|
2443
2582
|
}
|
|
2444
2583
|
.fb-tile-action-remove:hover {
|
|
2445
|
-
|
|
2584
|
+
color: #dc2626;
|
|
2446
2585
|
}
|
|
2447
2586
|
|
|
2448
|
-
/*
|
|
2587
|
+
/* Zoom popup action buttons always visible */
|
|
2449
2588
|
.fb-tile-zoom-preview .fb-tile-actions {
|
|
2450
2589
|
position: absolute;
|
|
2451
2590
|
top: 6px;
|
|
@@ -2454,116 +2593,145 @@ function ensureFileStyles() {
|
|
|
2454
2593
|
z-index: 10000;
|
|
2455
2594
|
}
|
|
2456
2595
|
|
|
2457
|
-
/*
|
|
2458
|
-
.fb-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2596
|
+
/* \u2500\u2500\u2500 Hover zoom preview popup \u2500\u2500\u2500 */
|
|
2597
|
+
.fb-tile-zoom-preview {
|
|
2598
|
+
position: fixed;
|
|
2599
|
+
z-index: 9999;
|
|
2600
|
+
background: var(--fb-background-color, #fff);
|
|
2601
|
+
border: 1px solid #e2e8f0;
|
|
2602
|
+
border-radius: 0.5rem;
|
|
2603
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
|
|
2604
|
+
padding: 4px;
|
|
2605
|
+
width: 350px;
|
|
2606
|
+
height: 350px;
|
|
2607
|
+
pointer-events: none;
|
|
2608
|
+
opacity: 0;
|
|
2609
|
+
transition: opacity 150ms ease;
|
|
2462
2610
|
}
|
|
2463
|
-
.fb-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2611
|
+
.fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
|
|
2612
|
+
opacity: 1;
|
|
2613
|
+
}
|
|
2614
|
+
.fb-tile-zoom-preview-img {
|
|
2615
|
+
width: 100%;
|
|
2616
|
+
height: 100%;
|
|
2617
|
+
object-fit: contain;
|
|
2618
|
+
display: block;
|
|
2619
|
+
border-radius: calc(0.5rem - 2px);
|
|
2467
2620
|
}
|
|
2468
2621
|
|
|
2469
|
-
/*
|
|
2470
|
-
.fb-
|
|
2471
|
-
height:
|
|
2472
|
-
border:
|
|
2473
|
-
border
|
|
2622
|
+
/* \u2500\u2500\u2500 Single-file uploading state \u2500\u2500\u2500 */
|
|
2623
|
+
.fb-single-uploading {
|
|
2624
|
+
height: 180px;
|
|
2625
|
+
border-radius: 0.75rem;
|
|
2626
|
+
border: 1px dashed #60a5fa;
|
|
2627
|
+
background: rgba(239,246,255,0.5);
|
|
2474
2628
|
display: flex;
|
|
2475
2629
|
flex-direction: column;
|
|
2476
2630
|
align-items: center;
|
|
2477
2631
|
justify-content: center;
|
|
2478
|
-
gap:
|
|
2479
|
-
cursor: pointer;
|
|
2480
|
-
background: none;
|
|
2481
|
-
padding: 0;
|
|
2482
|
-
transition:
|
|
2483
|
-
border-color var(--fb-transition-duration, 200ms),
|
|
2484
|
-
background var(--fb-transition-duration, 200ms);
|
|
2485
|
-
width: 100%;
|
|
2632
|
+
gap: 8px;
|
|
2486
2633
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2634
|
+
|
|
2635
|
+
/* \u2500\u2500\u2500 Video overlays \u2500\u2500\u2500 */
|
|
2636
|
+
.fb-video-overlay {
|
|
2637
|
+
position: absolute;
|
|
2638
|
+
inset: 0;
|
|
2639
|
+
display: flex;
|
|
2640
|
+
align-items: center;
|
|
2641
|
+
justify-content: center;
|
|
2642
|
+
background: rgba(0,0,0,0.25);
|
|
2492
2643
|
}
|
|
2493
|
-
.fb-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2644
|
+
.fb-play-btn {
|
|
2645
|
+
background: rgba(255,255,255,0.9);
|
|
2646
|
+
border-radius: 50%;
|
|
2647
|
+
display: flex;
|
|
2648
|
+
align-items: center;
|
|
2649
|
+
justify-content: center;
|
|
2497
2650
|
}
|
|
2498
|
-
.fb-
|
|
2499
|
-
|
|
2500
|
-
|
|
2651
|
+
.fb-video-preview-wrap {
|
|
2652
|
+
position: relative;
|
|
2653
|
+
width: 100%;
|
|
2654
|
+
height: 100%;
|
|
2655
|
+
}
|
|
2656
|
+
.fb-video-btn-overlay {
|
|
2657
|
+
position: absolute;
|
|
2658
|
+
top: 8px;
|
|
2659
|
+
right: 8px;
|
|
2660
|
+
z-index: 10;
|
|
2661
|
+
display: flex;
|
|
2662
|
+
gap: 4px;
|
|
2663
|
+
opacity: 0;
|
|
2664
|
+
transition: opacity 150ms;
|
|
2665
|
+
pointer-events: none;
|
|
2666
|
+
}
|
|
2667
|
+
.fb-video-preview-wrap:hover .fb-video-btn-overlay {
|
|
2668
|
+
opacity: 1;
|
|
2669
|
+
pointer-events: auto;
|
|
2501
2670
|
}
|
|
2502
|
-
.fb-
|
|
2671
|
+
.fb-video-btn {
|
|
2672
|
+
border: none;
|
|
2673
|
+
border-radius: 4px;
|
|
2503
2674
|
font-size: 11px;
|
|
2504
|
-
|
|
2675
|
+
padding: 4px 8px;
|
|
2676
|
+
cursor: pointer;
|
|
2677
|
+
color: #fff;
|
|
2678
|
+
line-height: 1.2;
|
|
2505
2679
|
}
|
|
2680
|
+
.fb-video-btn-delete { background: rgba(220,38,38,0.85); }
|
|
2681
|
+
.fb-video-btn-delete:hover { background: rgba(185,28,28,0.95); }
|
|
2682
|
+
.fb-video-btn-change { background: rgba(31,41,55,0.85); }
|
|
2683
|
+
.fb-video-btn-change:hover { background: rgba(17,24,39,0.95); }
|
|
2506
2684
|
|
|
2507
|
-
/*
|
|
2508
|
-
.fb-tile
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
justify-content: center;
|
|
2513
|
-
cursor: pointer;
|
|
2514
|
-
font-size: 24px;
|
|
2515
|
-
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2516
|
-
transition:
|
|
2517
|
-
border-color var(--fb-transition-duration, 200ms),
|
|
2518
|
-
color var(--fb-transition-duration, 200ms);
|
|
2519
|
-
background: none;
|
|
2520
|
-
padding: 0;
|
|
2521
|
-
width: var(--fb-tile-size, 160px);
|
|
2522
|
-
height: var(--fb-tile-size, 160px);
|
|
2523
|
-
flex-shrink: 0;
|
|
2524
|
-
position: relative;
|
|
2685
|
+
/* \u2500\u2500\u2500 Readonly readonly tile \u2500\u2500\u2500 */
|
|
2686
|
+
.fb-readonly-tile {
|
|
2687
|
+
aspect-ratio: 1 / 1;
|
|
2688
|
+
border-radius: 0.5rem;
|
|
2689
|
+
border: 1px solid #e2e8f0;
|
|
2525
2690
|
overflow: hidden;
|
|
2526
|
-
|
|
2691
|
+
position: relative;
|
|
2692
|
+
cursor: pointer;
|
|
2527
2693
|
}
|
|
2528
|
-
.fb-tile
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2694
|
+
.fb-readonly-tile img {
|
|
2695
|
+
width: 100%;
|
|
2696
|
+
height: 100%;
|
|
2697
|
+
object-fit: contain;
|
|
2698
|
+
display: block;
|
|
2533
2699
|
}
|
|
2534
|
-
|
|
2535
|
-
/* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
|
|
2536
|
-
.fb-tile-zoom-preview {
|
|
2537
|
-
position: fixed;
|
|
2538
|
-
z-index: 9999;
|
|
2539
|
-
background: var(--fb-background-color, #fff);
|
|
2540
|
-
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2541
|
-
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2542
|
-
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
|
|
2543
|
-
padding: 4px;
|
|
2544
|
-
width: 350px;
|
|
2545
|
-
height: 350px;
|
|
2546
|
-
pointer-events: none;
|
|
2700
|
+
.fb-readonly-tile .fb-tile-actions {
|
|
2547
2701
|
opacity: 0;
|
|
2548
|
-
transition: opacity 150ms ease;
|
|
2549
2702
|
}
|
|
2550
|
-
.fb-tile
|
|
2703
|
+
.fb-readonly-tile:hover .fb-tile-actions {
|
|
2551
2704
|
opacity: 1;
|
|
2552
2705
|
}
|
|
2553
|
-
|
|
2706
|
+
|
|
2707
|
+
/* \u2500\u2500\u2500 Readonly single-file filled \u2500\u2500\u2500 */
|
|
2708
|
+
.fb-single-readonly-filled {
|
|
2709
|
+
position: relative;
|
|
2710
|
+
border-radius: 0.75rem;
|
|
2711
|
+
border: 1px solid #e2e8f0;
|
|
2712
|
+
overflow: hidden;
|
|
2713
|
+
height: 220px;
|
|
2714
|
+
display: block;
|
|
2715
|
+
cursor: pointer;
|
|
2716
|
+
}
|
|
2717
|
+
.fb-single-readonly-filled img {
|
|
2554
2718
|
width: 100%;
|
|
2555
2719
|
height: 100%;
|
|
2556
2720
|
object-fit: contain;
|
|
2557
2721
|
display: block;
|
|
2558
|
-
|
|
2559
|
-
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/* \u2500\u2500\u2500 Readonly multi grid \u2500\u2500\u2500 */
|
|
2725
|
+
.fb-multi-readonly-grid {
|
|
2726
|
+
display: grid;
|
|
2727
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
2728
|
+
gap: 10px;
|
|
2560
2729
|
}
|
|
2561
2730
|
`;
|
|
2562
2731
|
document.head.appendChild(style);
|
|
2563
2732
|
}
|
|
2564
2733
|
|
|
2565
2734
|
// src/components/file/dom.ts
|
|
2566
|
-
var TILE_SIZE = "160px";
|
|
2567
2735
|
function createFileTile() {
|
|
2568
2736
|
ensureFileStyles();
|
|
2569
2737
|
const tile = document.createElement("div");
|
|
@@ -2571,7 +2739,7 @@ function createFileTile() {
|
|
|
2571
2739
|
return tile;
|
|
2572
2740
|
}
|
|
2573
2741
|
function showFileError(container, message) {
|
|
2574
|
-
const existing = container.closest("
|
|
2742
|
+
const existing = container.closest("[data-files-wrapper]")?.querySelector(".file-error-message");
|
|
2575
2743
|
if (existing) existing.remove();
|
|
2576
2744
|
const errorEl = document.createElement("div");
|
|
2577
2745
|
errorEl.className = "file-error-message error-message";
|
|
@@ -2581,10 +2749,10 @@ function showFileError(container, message) {
|
|
|
2581
2749
|
margin-top: 0.25rem;
|
|
2582
2750
|
`;
|
|
2583
2751
|
errorEl.textContent = message;
|
|
2584
|
-
container.closest("
|
|
2752
|
+
container.closest("[data-files-wrapper]")?.appendChild(errorEl);
|
|
2585
2753
|
}
|
|
2586
2754
|
function clearFileError(container) {
|
|
2587
|
-
const existing = container.closest("
|
|
2755
|
+
const existing = container.closest("[data-files-wrapper]")?.querySelector(".file-error-message");
|
|
2588
2756
|
if (existing) existing.remove();
|
|
2589
2757
|
}
|
|
2590
2758
|
function addDeleteButton(container, state, onDelete) {
|
|
@@ -2602,13 +2770,6 @@ function addDeleteButton(container, state, onDelete) {
|
|
|
2602
2770
|
overlay.appendChild(deleteBtn);
|
|
2603
2771
|
container.appendChild(overlay);
|
|
2604
2772
|
}
|
|
2605
|
-
function findFilePicker(container) {
|
|
2606
|
-
let el = container.parentElement;
|
|
2607
|
-
while (el && !el.dataset.filesWrapper) {
|
|
2608
|
-
el = el.parentElement;
|
|
2609
|
-
}
|
|
2610
|
-
return el?.querySelector('input[type="file"]') ?? null;
|
|
2611
|
-
}
|
|
2612
2773
|
function createUploadingTile(fileName, state) {
|
|
2613
2774
|
ensureFileStyles();
|
|
2614
2775
|
const tile = createFileTile();
|
|
@@ -2624,10 +2785,13 @@ function createUploadingTile(fileName, state) {
|
|
|
2624
2785
|
return tile;
|
|
2625
2786
|
}
|
|
2626
2787
|
function ensureTilesWrap(list) {
|
|
2788
|
+
const existingGrid = list.querySelector(".fb-multi-grid");
|
|
2789
|
+
if (existingGrid) return existingGrid;
|
|
2627
2790
|
const existing = list.querySelector(".fb-tiles-wrap");
|
|
2628
2791
|
if (existing) return existing;
|
|
2629
|
-
|
|
2630
|
-
|
|
2792
|
+
list.querySelector(".fb-file-dropzone")?.remove();
|
|
2793
|
+
list.querySelector(".fb-wide-tile")?.remove();
|
|
2794
|
+
list.querySelector(".fb-multi-outer")?.remove();
|
|
2631
2795
|
const tilesWrap = document.createElement("div");
|
|
2632
2796
|
tilesWrap.className = "fb-tiles-wrap";
|
|
2633
2797
|
tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
|
|
@@ -2639,7 +2803,7 @@ function ensureTilesWrap(list) {
|
|
|
2639
2803
|
return tilesWrap;
|
|
2640
2804
|
}
|
|
2641
2805
|
function setEmptyFileContainer(fileContainer, state, hint) {
|
|
2642
|
-
const hintHtml =
|
|
2806
|
+
const hintHtml = "";
|
|
2643
2807
|
fileContainer.innerHTML = `
|
|
2644
2808
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2645
2809
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
@@ -2681,9 +2845,11 @@ function setupDragAndDrop(element, dropHandler) {
|
|
|
2681
2845
|
}
|
|
2682
2846
|
|
|
2683
2847
|
// src/components/file/preview.ts
|
|
2684
|
-
var ICON_DOWNLOAD = `<svg width="
|
|
2685
|
-
var ICON_OPEN = `<svg width="
|
|
2686
|
-
var ICON_REMOVE = `<svg width="
|
|
2848
|
+
var ICON_DOWNLOAD = `<svg width="16" height="16" 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>`;
|
|
2849
|
+
var ICON_OPEN = `<svg width="16" height="16" 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>`;
|
|
2850
|
+
var ICON_REMOVE = `<svg width="16" height="16" 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>`;
|
|
2851
|
+
var ICON_REPLACE = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 18a4 4 0 000-8 6 6 0 00-11.5 2A4 4 0 006 20h11M12 12v7M12 12l-3 3M12 12l3 3"/></svg>`;
|
|
2852
|
+
var ICON_LIBRARY = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h4v16H4z"/><path d="M10 4h4v16h-4z"/><path d="M16 5l3.5 1-3 14L13 19"/></svg>`;
|
|
2687
2853
|
function canDownload(state, meta) {
|
|
2688
2854
|
return Boolean(
|
|
2689
2855
|
state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || meta?.file
|
|
@@ -2695,7 +2861,16 @@ function canOpenInTab(state, meta) {
|
|
|
2695
2861
|
);
|
|
2696
2862
|
}
|
|
2697
2863
|
function createTileActions(options) {
|
|
2698
|
-
const {
|
|
2864
|
+
const {
|
|
2865
|
+
canRemove,
|
|
2866
|
+
removeHandler,
|
|
2867
|
+
state,
|
|
2868
|
+
resourceId,
|
|
2869
|
+
fileName,
|
|
2870
|
+
meta,
|
|
2871
|
+
replaceHandler,
|
|
2872
|
+
libraryHandler
|
|
2873
|
+
} = options;
|
|
2699
2874
|
const group = document.createElement("div");
|
|
2700
2875
|
group.className = "fb-tile-actions";
|
|
2701
2876
|
const makeBtn = (icon, label, cls) => {
|
|
@@ -2710,15 +2885,41 @@ function createTileActions(options) {
|
|
|
2710
2885
|
});
|
|
2711
2886
|
return btn;
|
|
2712
2887
|
};
|
|
2888
|
+
if (replaceHandler) {
|
|
2889
|
+
const replaceBtn = makeBtn(
|
|
2890
|
+
ICON_REPLACE,
|
|
2891
|
+
t("replaceFile", state),
|
|
2892
|
+
"fb-tile-action-replace"
|
|
2893
|
+
);
|
|
2894
|
+
replaceBtn.addEventListener("click", () => replaceHandler());
|
|
2895
|
+
group.appendChild(replaceBtn);
|
|
2896
|
+
}
|
|
2897
|
+
if (libraryHandler) {
|
|
2898
|
+
const libBtn = makeBtn(
|
|
2899
|
+
ICON_LIBRARY,
|
|
2900
|
+
t("fromLibrary", state),
|
|
2901
|
+
"fb-tile-action-library"
|
|
2902
|
+
);
|
|
2903
|
+
libBtn.addEventListener("click", () => libraryHandler());
|
|
2904
|
+
group.appendChild(libBtn);
|
|
2905
|
+
}
|
|
2713
2906
|
if (canDownload(state, meta)) {
|
|
2714
|
-
const dlBtn = makeBtn(
|
|
2907
|
+
const dlBtn = makeBtn(
|
|
2908
|
+
ICON_DOWNLOAD,
|
|
2909
|
+
t("downloadFile", state),
|
|
2910
|
+
"fb-tile-action-download"
|
|
2911
|
+
);
|
|
2715
2912
|
dlBtn.addEventListener("click", () => {
|
|
2716
2913
|
triggerTileDownload(resourceId, fileName, state, meta);
|
|
2717
2914
|
});
|
|
2718
2915
|
group.appendChild(dlBtn);
|
|
2719
2916
|
}
|
|
2720
2917
|
if (canOpenInTab(state, meta)) {
|
|
2721
|
-
const openBtn = makeBtn(
|
|
2918
|
+
const openBtn = makeBtn(
|
|
2919
|
+
ICON_OPEN,
|
|
2920
|
+
t("openInNewTab", state),
|
|
2921
|
+
"fb-tile-action-open"
|
|
2922
|
+
);
|
|
2722
2923
|
openBtn.addEventListener("click", () => {
|
|
2723
2924
|
triggerTileOpen(resourceId, state, meta).catch((err) => {
|
|
2724
2925
|
console.error("Open failed:", err);
|
|
@@ -2727,7 +2928,11 @@ function createTileActions(options) {
|
|
|
2727
2928
|
group.appendChild(openBtn);
|
|
2728
2929
|
}
|
|
2729
2930
|
if (canRemove && removeHandler) {
|
|
2730
|
-
const rmBtn = makeBtn(
|
|
2931
|
+
const rmBtn = makeBtn(
|
|
2932
|
+
ICON_REMOVE,
|
|
2933
|
+
t("removeElement", state),
|
|
2934
|
+
"fb-tile-action-remove"
|
|
2935
|
+
);
|
|
2731
2936
|
rmBtn.addEventListener("click", () => {
|
|
2732
2937
|
removeHandler();
|
|
2733
2938
|
});
|
|
@@ -2805,11 +3010,17 @@ function positionZoomPopup(popup, tile) {
|
|
|
2805
3010
|
} else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
|
|
2806
3011
|
top = tileRect.bottom + margin;
|
|
2807
3012
|
} else {
|
|
2808
|
-
top = Math.max(
|
|
3013
|
+
top = Math.max(
|
|
3014
|
+
padding,
|
|
3015
|
+
Math.min(window.innerHeight - popupSize - padding, tileRect.top)
|
|
3016
|
+
);
|
|
2809
3017
|
}
|
|
2810
3018
|
const tileCenterX = tileRect.left + tileRect.width / 2;
|
|
2811
3019
|
let left = tileCenterX - popupSize / 2;
|
|
2812
|
-
left = Math.max(
|
|
3020
|
+
left = Math.max(
|
|
3021
|
+
padding,
|
|
3022
|
+
Math.min(window.innerWidth - popupSize - padding, left)
|
|
3023
|
+
);
|
|
2813
3024
|
popup.style.top = `${top}px`;
|
|
2814
3025
|
popup.style.left = `${left}px`;
|
|
2815
3026
|
}
|
|
@@ -2853,7 +3064,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
|
|
|
2853
3064
|
const popup = getOrCreateZoomPopup();
|
|
2854
3065
|
const existingActions = popup.querySelector(".fb-tile-actions");
|
|
2855
3066
|
if (existingActions) existingActions.remove();
|
|
2856
|
-
const img = popup.querySelector(
|
|
3067
|
+
const img = popup.querySelector(
|
|
3068
|
+
".fb-tile-zoom-preview-img"
|
|
3069
|
+
);
|
|
2857
3070
|
img.src = src;
|
|
2858
3071
|
img.alt = alt;
|
|
2859
3072
|
if (actionsEl) {
|
|
@@ -2881,7 +3094,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
|
|
|
2881
3094
|
});
|
|
2882
3095
|
}
|
|
2883
3096
|
function attachClonedActionListeners(cloned, original) {
|
|
2884
|
-
const originalBtns = Array.from(
|
|
3097
|
+
const originalBtns = Array.from(
|
|
3098
|
+
original.querySelectorAll(".fb-tile-action-btn")
|
|
3099
|
+
);
|
|
2885
3100
|
const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
|
|
2886
3101
|
clonedBtns.forEach((clonedBtn, i) => {
|
|
2887
3102
|
const origBtn = originalBtns[i];
|
|
@@ -2895,8 +3110,7 @@ function attachClonedActionListeners(cloned, original) {
|
|
|
2895
3110
|
}
|
|
2896
3111
|
function renderLocalImagePreview(container, file, fileName, state) {
|
|
2897
3112
|
const img = document.createElement("img");
|
|
2898
|
-
img.
|
|
2899
|
-
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
3113
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
2900
3114
|
img.alt = fileName || t("previewAlt", state);
|
|
2901
3115
|
const reader = new FileReader();
|
|
2902
3116
|
reader.onload = (e) => {
|
|
@@ -2918,7 +3132,7 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
2918
3132
|
const newContainer = setupDragDropless(container);
|
|
2919
3133
|
newContainer.innerHTML = `
|
|
2920
3134
|
<div class="fb-video-preview-wrap">
|
|
2921
|
-
<video
|
|
3135
|
+
<video style="width:100%;height:100%;object-fit:contain;" controls preload="auto" muted src="${videoUrl}">
|
|
2922
3136
|
${escapeHtml(t("videoNotSupported", state))}
|
|
2923
3137
|
</video>
|
|
2924
3138
|
<div class="fb-video-btn-overlay">
|
|
@@ -2935,14 +3149,18 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
2935
3149
|
return newContainer;
|
|
2936
3150
|
}
|
|
2937
3151
|
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
2938
|
-
const changeBtn = container.querySelector(
|
|
3152
|
+
const changeBtn = container.querySelector(
|
|
3153
|
+
".change-file-btn"
|
|
3154
|
+
);
|
|
2939
3155
|
if (changeBtn) {
|
|
2940
3156
|
changeBtn.onclick = (e) => {
|
|
2941
3157
|
e.stopPropagation();
|
|
2942
3158
|
deps?.picker?.click();
|
|
2943
3159
|
};
|
|
2944
3160
|
}
|
|
2945
|
-
const deleteBtn = container.querySelector(
|
|
3161
|
+
const deleteBtn = container.querySelector(
|
|
3162
|
+
".delete-file-btn"
|
|
3163
|
+
);
|
|
2946
3164
|
if (deleteBtn) {
|
|
2947
3165
|
deleteBtn.onclick = (e) => {
|
|
2948
3166
|
e.stopPropagation();
|
|
@@ -2962,11 +3180,11 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
2962
3180
|
container.onclick = deps.fileUploadHandler;
|
|
2963
3181
|
}
|
|
2964
3182
|
container.innerHTML = `
|
|
2965
|
-
<div
|
|
2966
|
-
<svg
|
|
3183
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
|
|
3184
|
+
<svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
|
|
2967
3185
|
<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"/>
|
|
2968
3186
|
</svg>
|
|
2969
|
-
<div
|
|
3187
|
+
<div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
|
|
2970
3188
|
</div>
|
|
2971
3189
|
`;
|
|
2972
3190
|
if (deps?.setupDrop) {
|
|
@@ -2983,11 +3201,11 @@ function renderDeleteButton(container, resourceId, state) {
|
|
|
2983
3201
|
hiddenInput.value = "";
|
|
2984
3202
|
}
|
|
2985
3203
|
container.innerHTML = `
|
|
2986
|
-
<div
|
|
2987
|
-
<svg
|
|
3204
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
|
|
3205
|
+
<svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
|
|
2988
3206
|
<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"/>
|
|
2989
3207
|
</svg>
|
|
2990
|
-
<div
|
|
3208
|
+
<div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
|
|
2991
3209
|
</div>
|
|
2992
3210
|
`;
|
|
2993
3211
|
});
|
|
@@ -3006,7 +3224,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
3006
3224
|
deps
|
|
3007
3225
|
);
|
|
3008
3226
|
} else {
|
|
3009
|
-
container.innerHTML = `<div
|
|
3227
|
+
container.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);"><div style="font-size:36px;margin-bottom:0.5rem;">\u{1F4C1}</div><div style="font-size:0.875rem;">${escapeHtml(fileName)}</div></div>`;
|
|
3010
3228
|
}
|
|
3011
3229
|
if (!isReadonly && !meta.type?.startsWith("video/")) {
|
|
3012
3230
|
renderDeleteButton(container, resourceId, state);
|
|
@@ -3014,7 +3232,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
3014
3232
|
}
|
|
3015
3233
|
function renderUploadedVideoPreview(container, thumbnailUrl, state) {
|
|
3016
3234
|
const video = document.createElement("video");
|
|
3017
|
-
video.
|
|
3235
|
+
video.style.cssText = "width:100%;height:100%;object-fit:contain;";
|
|
3018
3236
|
video.controls = true;
|
|
3019
3237
|
video.preload = "metadata";
|
|
3020
3238
|
video.muted = true;
|
|
@@ -3035,8 +3253,7 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
3035
3253
|
renderUploadedVideoPreview(container, thumbnailUrl, state);
|
|
3036
3254
|
} else {
|
|
3037
3255
|
const img = document.createElement("img");
|
|
3038
|
-
img.
|
|
3039
|
-
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
3256
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3040
3257
|
img.alt = fileName || t("previewAlt", state);
|
|
3041
3258
|
img.src = thumbnailUrl;
|
|
3042
3259
|
container.appendChild(img);
|
|
@@ -3047,11 +3264,11 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
3047
3264
|
} catch (error) {
|
|
3048
3265
|
console.error("Failed to get thumbnail:", error);
|
|
3049
3266
|
container.innerHTML = `
|
|
3050
|
-
<div
|
|
3051
|
-
<svg
|
|
3267
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
|
|
3268
|
+
<svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
|
|
3052
3269
|
<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"/>
|
|
3053
3270
|
</svg>
|
|
3054
|
-
<div
|
|
3271
|
+
<div style="font-size:0.875rem;text-align:center;">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
|
|
3055
3272
|
</div>
|
|
3056
3273
|
`;
|
|
3057
3274
|
}
|
|
@@ -3076,7 +3293,13 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
3076
3293
|
deps
|
|
3077
3294
|
);
|
|
3078
3295
|
} else {
|
|
3079
|
-
await renderUploadedFilePreview(
|
|
3296
|
+
await renderUploadedFilePreview(
|
|
3297
|
+
container,
|
|
3298
|
+
resourceId,
|
|
3299
|
+
fileName,
|
|
3300
|
+
meta,
|
|
3301
|
+
state
|
|
3302
|
+
);
|
|
3080
3303
|
const isVideo = meta?.type?.startsWith("video/");
|
|
3081
3304
|
if (!isReadonly && !isVideo) {
|
|
3082
3305
|
renderDeleteButton(container, resourceId, state);
|
|
@@ -3103,7 +3326,8 @@ async function renderFilePreviewReadonly(resourceId, state, fileName, options =
|
|
|
3103
3326
|
}
|
|
3104
3327
|
const localFileUrl = meta?.file instanceof File ? getLocalFileUrl(meta.file) : null;
|
|
3105
3328
|
const resolveOpenUrl = async () => {
|
|
3106
|
-
if (state.config.getDownloadUrl)
|
|
3329
|
+
if (state.config.getDownloadUrl)
|
|
3330
|
+
return state.config.getDownloadUrl(resourceId);
|
|
3107
3331
|
if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
|
|
3108
3332
|
return localFileUrl;
|
|
3109
3333
|
};
|
|
@@ -3225,13 +3449,9 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3225
3449
|
const img = document.createElement("img");
|
|
3226
3450
|
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3227
3451
|
img.alt = meta.name;
|
|
3228
|
-
|
|
3229
|
-
reader.onload = (e) => {
|
|
3230
|
-
img.src = e.target?.result || "";
|
|
3231
|
-
attachZoomHover(tile, img.src, meta.name, actionsEl ?? null);
|
|
3232
|
-
};
|
|
3233
|
-
reader.readAsDataURL(meta.file);
|
|
3452
|
+
img.src = getLocalFileUrl(meta.file);
|
|
3234
3453
|
tile.appendChild(img);
|
|
3454
|
+
attachZoomHover(tile, img.src, meta.name, actionsEl ?? null);
|
|
3235
3455
|
} else if (state.config.getThumbnail) {
|
|
3236
3456
|
try {
|
|
3237
3457
|
const url = await state.config.getThumbnail(rid);
|
|
@@ -3247,7 +3467,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3247
3467
|
}
|
|
3248
3468
|
} catch (error) {
|
|
3249
3469
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3250
|
-
if (state.config.onThumbnailError)
|
|
3470
|
+
if (state.config.onThumbnailError)
|
|
3471
|
+
state.config.onThumbnailError(err, rid);
|
|
3251
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>`;
|
|
3252
3473
|
}
|
|
3253
3474
|
} else {
|
|
@@ -3280,7 +3501,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3280
3501
|
}
|
|
3281
3502
|
} catch (error) {
|
|
3282
3503
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3283
|
-
if (state.config.onThumbnailError)
|
|
3504
|
+
if (state.config.onThumbnailError)
|
|
3505
|
+
state.config.onThumbnailError(err, rid);
|
|
3284
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>`;
|
|
3285
3507
|
}
|
|
3286
3508
|
} else {
|
|
@@ -3288,17 +3510,20 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
|
3288
3510
|
}
|
|
3289
3511
|
if (actionsEl) tile.appendChild(actionsEl);
|
|
3290
3512
|
} else {
|
|
3291
|
-
|
|
3292
|
-
const hasExtension = name.includes(".");
|
|
3293
|
-
const captionHtml = hasExtension ? `<div class="fb-tile-label">${escapeHtml(name.length > 10 ? name.substring(0, 8) + "\u2026" : name)}</div>` : "";
|
|
3294
|
-
tile.innerHTML = `
|
|
3295
|
-
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3296
|
-
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3297
|
-
${captionHtml}
|
|
3298
|
-
</div>`;
|
|
3299
|
-
if (actionsEl) tile.appendChild(actionsEl);
|
|
3513
|
+
fillDocumentFallback(tile, rid, meta, actionsEl);
|
|
3300
3514
|
}
|
|
3301
3515
|
}
|
|
3516
|
+
function fillDocumentFallback(tile, rid, meta, actionsEl) {
|
|
3517
|
+
const fileName = meta?.name ?? rid.split("/").pop() ?? "";
|
|
3518
|
+
if (fileName) tile.title = fileName;
|
|
3519
|
+
const labelHtml = fileName ? `<div style="font-size:11px;line-height:1.2;text-align:center;color:var(--fb-text-secondary-color,#6b7280);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(fileName)}</div>` : "";
|
|
3520
|
+
tile.innerHTML = `
|
|
3521
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3522
|
+
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3523
|
+
${labelHtml}
|
|
3524
|
+
</div>`;
|
|
3525
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
3526
|
+
}
|
|
3302
3527
|
async function forceDownload(resourceId, fileName, state) {
|
|
3303
3528
|
try {
|
|
3304
3529
|
let fileUrl = null;
|
|
@@ -3310,7 +3535,8 @@ async function forceDownload(resourceId, fileName, state) {
|
|
|
3310
3535
|
if (fileUrl) {
|
|
3311
3536
|
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
3312
3537
|
const response = await fetch(finalUrl);
|
|
3313
|
-
if (!response.ok)
|
|
3538
|
+
if (!response.ok)
|
|
3539
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3314
3540
|
const blob = await response.blob();
|
|
3315
3541
|
downloadBlob(blob, fileName);
|
|
3316
3542
|
} else {
|
|
@@ -3359,7 +3585,9 @@ async function uploadSingleFile(file, state) {
|
|
|
3359
3585
|
} catch (error) {
|
|
3360
3586
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
3361
3587
|
if (state.config.onUploadError) state.config.onUploadError(err, file);
|
|
3362
|
-
|
|
3588
|
+
const wrapped = new Error(`File upload failed: ${err.message}`);
|
|
3589
|
+
wrapped.cause = err;
|
|
3590
|
+
throw wrapped;
|
|
3363
3591
|
}
|
|
3364
3592
|
}
|
|
3365
3593
|
async function handleFileSelect(opts) {
|
|
@@ -3398,6 +3626,10 @@ async function handleFileSelect(opts) {
|
|
|
3398
3626
|
return;
|
|
3399
3627
|
}
|
|
3400
3628
|
clearFileError(container);
|
|
3629
|
+
const existingHiddenInput = container.parentElement?.querySelector(
|
|
3630
|
+
'input[type="hidden"]'
|
|
3631
|
+
);
|
|
3632
|
+
const previousRid = existingHiddenInput?.value || null;
|
|
3401
3633
|
ensureFileStyles();
|
|
3402
3634
|
container.innerHTML = `
|
|
3403
3635
|
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
|
|
@@ -3408,7 +3640,13 @@ async function handleFileSelect(opts) {
|
|
|
3408
3640
|
try {
|
|
3409
3641
|
rid = await uploadSingleFile(file, state);
|
|
3410
3642
|
} catch (error) {
|
|
3411
|
-
|
|
3643
|
+
if (previousRid && deps?.onAfterUpload) {
|
|
3644
|
+
deps.onAfterUpload(container, previousRid);
|
|
3645
|
+
} else if (deps?.onRemove) {
|
|
3646
|
+
deps.onRemove();
|
|
3647
|
+
} else {
|
|
3648
|
+
setEmptyFileContainer(container, state);
|
|
3649
|
+
}
|
|
3412
3650
|
throw error;
|
|
3413
3651
|
}
|
|
3414
3652
|
state.resourceIndex.set(rid, {
|
|
@@ -3418,9 +3656,10 @@ async function handleFileSelect(opts) {
|
|
|
3418
3656
|
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3419
3657
|
file
|
|
3420
3658
|
});
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3659
|
+
if (previousRid && previousRid !== rid) {
|
|
3660
|
+
releaseLocalFileUrl(state.resourceIndex.get(previousRid)?.file);
|
|
3661
|
+
}
|
|
3662
|
+
let hiddenInput = existingHiddenInput;
|
|
3424
3663
|
if (!hiddenInput) {
|
|
3425
3664
|
hiddenInput = document.createElement("input");
|
|
3426
3665
|
hiddenInput.type = "hidden";
|
|
@@ -3429,7 +3668,9 @@ async function handleFileSelect(opts) {
|
|
|
3429
3668
|
}
|
|
3430
3669
|
hiddenInput.value = rid;
|
|
3431
3670
|
const isVideo = file.type.startsWith("video/");
|
|
3432
|
-
if (!isVideo && deps) {
|
|
3671
|
+
if (!isVideo && deps?.onAfterUpload) {
|
|
3672
|
+
deps.onAfterUpload(container, rid);
|
|
3673
|
+
} else if (!isVideo && deps) {
|
|
3433
3674
|
renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
|
|
3434
3675
|
} else {
|
|
3435
3676
|
renderFilePreview(container, rid, state, {
|
|
@@ -3458,7 +3699,9 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3458
3699
|
const rejectedBySize = afterMime.filter(
|
|
3459
3700
|
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3460
3701
|
);
|
|
3461
|
-
const valid = afterMime.filter(
|
|
3702
|
+
const valid = afterMime.filter(
|
|
3703
|
+
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
3704
|
+
);
|
|
3462
3705
|
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3463
3706
|
const accepted = valid.slice(0, remaining);
|
|
3464
3707
|
const skippedByCount = valid.length - accepted.length;
|
|
@@ -3471,7 +3714,13 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3471
3714
|
if (rejectedByMime.length > 0) {
|
|
3472
3715
|
const mimes = constraints.allowedMimes.join(", ");
|
|
3473
3716
|
const names = rejectedByMime.map((f) => f.name).join(", ");
|
|
3474
|
-
errorParts.push(
|
|
3717
|
+
errorParts.push(
|
|
3718
|
+
t("invalidFileMime", state, {
|
|
3719
|
+
name: names,
|
|
3720
|
+
type: rejectedByMime.map((f) => f.type).join(", "),
|
|
3721
|
+
mimes
|
|
3722
|
+
})
|
|
3723
|
+
);
|
|
3475
3724
|
}
|
|
3476
3725
|
if (rejectedBySize.length > 0) {
|
|
3477
3726
|
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
@@ -3490,17 +3739,18 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3490
3739
|
return { accepted, errorMessage: errorParts.join(" \u2022 ") };
|
|
3491
3740
|
}
|
|
3492
3741
|
async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
3493
|
-
|
|
3742
|
+
if (listEl) {
|
|
3743
|
+
const tilesWrap = ensureTilesWrap(listEl);
|
|
3744
|
+
const addTile = tilesWrap.querySelector(".fb-multi-add-tile-js") ?? tilesWrap.querySelector(".fb-tile-add");
|
|
3745
|
+
if (addTile) addTile.style.display = "none";
|
|
3746
|
+
}
|
|
3747
|
+
const failures = [];
|
|
3748
|
+
await Promise.allSettled(
|
|
3494
3749
|
accepted.map(async (file) => {
|
|
3495
3750
|
const placeholder = createUploadingTile(file.name, state);
|
|
3496
3751
|
if (listEl) {
|
|
3497
3752
|
const tilesWrap = ensureTilesWrap(listEl);
|
|
3498
|
-
|
|
3499
|
-
if (addTile) {
|
|
3500
|
-
tilesWrap.insertBefore(placeholder, addTile);
|
|
3501
|
-
} else {
|
|
3502
|
-
tilesWrap.appendChild(placeholder);
|
|
3503
|
-
}
|
|
3753
|
+
tilesWrap.appendChild(placeholder);
|
|
3504
3754
|
}
|
|
3505
3755
|
try {
|
|
3506
3756
|
const rid = await uploadSingleFile(file, state);
|
|
@@ -3512,11 +3762,27 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
|
3512
3762
|
file: void 0
|
|
3513
3763
|
});
|
|
3514
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 });
|
|
3515
3770
|
} finally {
|
|
3516
3771
|
placeholder.remove();
|
|
3517
3772
|
}
|
|
3518
3773
|
})
|
|
3519
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;
|
|
3520
3786
|
}
|
|
3521
3787
|
function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3522
3788
|
setupDragAndDrop(filesContainer, async (files) => {
|
|
@@ -3532,7 +3798,13 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
|
|
|
3532
3798
|
clearFileError(filesContainer);
|
|
3533
3799
|
}
|
|
3534
3800
|
const list = filesContainer.querySelector(".files-list") ?? filesContainer;
|
|
3535
|
-
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
|
+
}
|
|
3536
3808
|
updateCallback();
|
|
3537
3809
|
if (instance && pathKey && !state.config.readonly) {
|
|
3538
3810
|
instance.triggerOnChange(pathKey, resourceIds);
|
|
@@ -3542,7 +3814,7 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
|
|
|
3542
3814
|
function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3543
3815
|
filesPicker.onchange = async () => {
|
|
3544
3816
|
if (!filesPicker.files) return;
|
|
3545
|
-
const wrapperEl = filesPicker.closest("
|
|
3817
|
+
const wrapperEl = filesPicker.closest("[data-files-wrapper]") || filesPicker.parentElement;
|
|
3546
3818
|
const { accepted, errorMessage } = filterAndSlice(
|
|
3547
3819
|
Array.from(filesPicker.files),
|
|
3548
3820
|
resourceIds.length,
|
|
@@ -3555,7 +3827,20 @@ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback
|
|
|
3555
3827
|
clearFileError(wrapperEl);
|
|
3556
3828
|
}
|
|
3557
3829
|
const listEl = wrapperEl?.querySelector(".files-list");
|
|
3558
|
-
|
|
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
|
+
}
|
|
3559
3844
|
updateCallback();
|
|
3560
3845
|
filesPicker.value = "";
|
|
3561
3846
|
if (instance && pathKey && !state.config.readonly) {
|
|
@@ -3587,10 +3872,17 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
|
|
|
3587
3872
|
}
|
|
3588
3873
|
if (!isMimeAllowed(resource.type, allowedMimes)) {
|
|
3589
3874
|
const mimes = allowedMimes.join(", ");
|
|
3590
|
-
return t("invalidFileMime", state, {
|
|
3875
|
+
return t("invalidFileMime", state, {
|
|
3876
|
+
name: resource.name,
|
|
3877
|
+
type: resource.type,
|
|
3878
|
+
mimes
|
|
3879
|
+
});
|
|
3591
3880
|
}
|
|
3592
3881
|
if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
|
|
3593
|
-
return t("fileTooLarge", state, {
|
|
3882
|
+
return t("fileTooLarge", state, {
|
|
3883
|
+
name: resource.name,
|
|
3884
|
+
maxSize: maxSizeMB
|
|
3885
|
+
});
|
|
3594
3886
|
}
|
|
3595
3887
|
return null;
|
|
3596
3888
|
}
|
|
@@ -3649,7 +3941,13 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
3649
3941
|
return true;
|
|
3650
3942
|
});
|
|
3651
3943
|
const validItems = deduped.filter((r) => {
|
|
3652
|
-
const err = validatePickedResource(
|
|
3944
|
+
const err = validatePickedResource(
|
|
3945
|
+
r,
|
|
3946
|
+
allowedExtensions,
|
|
3947
|
+
allowedMimes,
|
|
3948
|
+
maxSizeMB,
|
|
3949
|
+
state
|
|
3950
|
+
);
|
|
3653
3951
|
return err === null;
|
|
3654
3952
|
});
|
|
3655
3953
|
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
|
|
@@ -3693,20 +3991,32 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
|
|
|
3693
3991
|
}
|
|
3694
3992
|
if (picked.length === 0) return;
|
|
3695
3993
|
const first = picked[0];
|
|
3696
|
-
const validationError = validatePickedResource(
|
|
3994
|
+
const validationError = validatePickedResource(
|
|
3995
|
+
first,
|
|
3996
|
+
allowedExtensions,
|
|
3997
|
+
allowedMimes,
|
|
3998
|
+
maxSizeMB,
|
|
3999
|
+
state
|
|
4000
|
+
);
|
|
3697
4001
|
if (validationError !== null) {
|
|
3698
4002
|
showFileError(container, validationError);
|
|
3699
4003
|
return;
|
|
3700
4004
|
}
|
|
3701
4005
|
clearFileError(container);
|
|
3702
4006
|
registerPickedResource(first, state);
|
|
3703
|
-
let hiddenInput = fileWrapper.querySelector(
|
|
4007
|
+
let hiddenInput = fileWrapper.querySelector(
|
|
4008
|
+
'input[type="hidden"]'
|
|
4009
|
+
);
|
|
3704
4010
|
if (!hiddenInput) {
|
|
3705
4011
|
hiddenInput = document.createElement("input");
|
|
3706
4012
|
hiddenInput.type = "hidden";
|
|
3707
4013
|
hiddenInput.name = pathKey;
|
|
3708
4014
|
fileWrapper.appendChild(hiddenInput);
|
|
3709
4015
|
}
|
|
4016
|
+
const previousRid = hiddenInput.value || null;
|
|
4017
|
+
if (previousRid && previousRid !== first.resourceId) {
|
|
4018
|
+
releaseLocalFileUrl(state.resourceIndex.get(previousRid)?.file);
|
|
4019
|
+
}
|
|
3710
4020
|
hiddenInput.value = first.resourceId;
|
|
3711
4021
|
await renderCallback(first.resourceId);
|
|
3712
4022
|
if (!state.config.readonly) {
|
|
@@ -3715,7 +4025,9 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
|
|
|
3715
4025
|
}
|
|
3716
4026
|
|
|
3717
4027
|
// src/components/file/render-edit.ts
|
|
3718
|
-
|
|
4028
|
+
var ICON_CLOUD = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 18a4 4 0 000-8 6 6 0 00-11.5 2A4 4 0 006 20h11M12 12v7M12 12l-3 3M12 12l3 3"/></svg>`;
|
|
4029
|
+
var ICON_LIBRARY2 = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h4v16H4z"/><path d="M10 4h4v16h-4z"/><path d="M16 5l3.5 1-3 14L13 19"/></svg>`;
|
|
4030
|
+
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps, extras) {
|
|
3719
4031
|
seedInferredResource(initial, state.resourceIndex);
|
|
3720
4032
|
const meta = state.resourceIndex.get(initial);
|
|
3721
4033
|
const isVideo = meta?.type?.startsWith("video/");
|
|
@@ -3726,7 +4038,7 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
|
|
|
3726
4038
|
deps
|
|
3727
4039
|
}).catch(console.error);
|
|
3728
4040
|
} else {
|
|
3729
|
-
|
|
4041
|
+
renderSingleFileFilled(fileContainer, initial, state, deps, extras);
|
|
3730
4042
|
}
|
|
3731
4043
|
const hiddenInput = document.createElement("input");
|
|
3732
4044
|
hiddenInput.type = "hidden";
|
|
@@ -3734,161 +4046,426 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
|
|
|
3734
4046
|
hiddenInput.value = initial;
|
|
3735
4047
|
fileWrapper.appendChild(hiddenInput);
|
|
3736
4048
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
const
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
4049
|
+
function buildWideTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false, constraintHint = "") {
|
|
4050
|
+
ensureFileStyles();
|
|
4051
|
+
const outer = document.createElement("div");
|
|
4052
|
+
outer.className = `fb-wide-tile${hasLibrary ? " fb-file-card-row" : ""}${isDragOver ? " fb-drag-over" : ""}`;
|
|
4053
|
+
const uploadBtn = document.createElement("button");
|
|
4054
|
+
uploadBtn.type = "button";
|
|
4055
|
+
uploadBtn.className = "fb-wide-tile-upload fb-file-dropzone";
|
|
4056
|
+
const cloudIcon = document.createElement("span");
|
|
4057
|
+
cloudIcon.style.cssText = "width:36px;height:36px;display:block;flex-shrink:0;";
|
|
4058
|
+
cloudIcon.innerHTML = ICON_CLOUD;
|
|
4059
|
+
uploadBtn.appendChild(cloudIcon);
|
|
4060
|
+
const primaryText = document.createElement("div");
|
|
4061
|
+
primaryText.className = "fb-wide-tile-label";
|
|
4062
|
+
primaryText.style.cssText = "font-size:14px;font-weight:600;";
|
|
4063
|
+
primaryText.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragText", state);
|
|
4064
|
+
uploadBtn.appendChild(primaryText);
|
|
4065
|
+
if (constraintHint) {
|
|
4066
|
+
const hintEl = document.createElement("div");
|
|
4067
|
+
hintEl.style.cssText = "font-size:11px;opacity:0.65;margin-top:2px;";
|
|
4068
|
+
hintEl.textContent = constraintHint;
|
|
4069
|
+
uploadBtn.appendChild(hintEl);
|
|
4070
|
+
}
|
|
4071
|
+
uploadBtn.onclick = (e) => {
|
|
4072
|
+
e.stopPropagation();
|
|
4073
|
+
onUploadClick();
|
|
4074
|
+
};
|
|
4075
|
+
outer.appendChild(uploadBtn);
|
|
4076
|
+
if (hasLibrary && onLibraryClick) {
|
|
4077
|
+
const divider = document.createElement("div");
|
|
4078
|
+
divider.className = "fb-wide-tile-divider";
|
|
4079
|
+
outer.appendChild(divider);
|
|
4080
|
+
const libBtn = document.createElement("button");
|
|
4081
|
+
libBtn.type = "button";
|
|
4082
|
+
libBtn.className = "fb-wide-tile-library fb-file-library-card";
|
|
4083
|
+
const libIcon = document.createElement("span");
|
|
4084
|
+
libIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
|
|
4085
|
+
libIcon.innerHTML = ICON_LIBRARY2;
|
|
4086
|
+
libBtn.appendChild(libIcon);
|
|
4087
|
+
const libLabel = document.createElement("div");
|
|
4088
|
+
libLabel.style.cssText = "font-size:13px;font-weight:600;text-align:center;";
|
|
4089
|
+
libLabel.textContent = t("fromLibrary", state);
|
|
4090
|
+
libBtn.appendChild(libLabel);
|
|
4091
|
+
const libHint = document.createElement("div");
|
|
4092
|
+
libHint.style.cssText = "font-size:11px;opacity:0.75;text-align:center;";
|
|
4093
|
+
libHint.textContent = t("libraryHint", state);
|
|
4094
|
+
libBtn.appendChild(libHint);
|
|
4095
|
+
libBtn.onclick = (e) => {
|
|
4096
|
+
e.stopPropagation();
|
|
4097
|
+
onLibraryClick();
|
|
4098
|
+
};
|
|
4099
|
+
outer.appendChild(libBtn);
|
|
4100
|
+
}
|
|
4101
|
+
attachDragOverFeedback(outer, {
|
|
4102
|
+
onEnter: () => {
|
|
4103
|
+
const primaryText2 = outer.querySelector(".fb-wide-tile-label");
|
|
4104
|
+
if (primaryText2) primaryText2.textContent = t("dropToUpload", state);
|
|
4105
|
+
},
|
|
4106
|
+
onLeave: () => {
|
|
4107
|
+
const primaryText2 = outer.querySelector(".fb-wide-tile-label");
|
|
4108
|
+
if (primaryText2) primaryText2.textContent = t("clickDragText", state);
|
|
4109
|
+
},
|
|
4110
|
+
activeClass: "fb-drag-over"
|
|
4111
|
+
});
|
|
4112
|
+
return outer;
|
|
3750
4113
|
}
|
|
3751
|
-
function
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4114
|
+
function attachDragOverFeedback(el, hooks) {
|
|
4115
|
+
let depth = 0;
|
|
4116
|
+
el.addEventListener("dragover", (e) => {
|
|
4117
|
+
e.preventDefault();
|
|
4118
|
+
});
|
|
4119
|
+
el.addEventListener("dragenter", (e) => {
|
|
4120
|
+
e.preventDefault();
|
|
4121
|
+
depth++;
|
|
4122
|
+
if (depth === 1) {
|
|
4123
|
+
el.classList.add(hooks.activeClass);
|
|
4124
|
+
hooks.onEnter();
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
el.addEventListener("dragleave", (e) => {
|
|
4128
|
+
e.preventDefault();
|
|
4129
|
+
depth = Math.max(0, depth - 1);
|
|
4130
|
+
if (depth === 0) {
|
|
4131
|
+
el.classList.remove(hooks.activeClass);
|
|
4132
|
+
hooks.onLeave();
|
|
4133
|
+
}
|
|
4134
|
+
});
|
|
4135
|
+
el.addEventListener("drop", () => {
|
|
4136
|
+
depth = 0;
|
|
4137
|
+
el.classList.remove(hooks.activeClass);
|
|
4138
|
+
hooks.onLeave();
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
function renderSingleFileFilled(fileContainer, resourceId, state, deps, extras) {
|
|
4142
|
+
const meta = state.resourceIndex.get(resourceId);
|
|
4143
|
+
const isVideo = meta?.type?.startsWith("video/");
|
|
4144
|
+
if (isVideo) {
|
|
4145
|
+
renderFilePreview(fileContainer, resourceId, state, {
|
|
4146
|
+
fileName: meta?.name ?? "",
|
|
4147
|
+
isReadonly: false,
|
|
4148
|
+
deps
|
|
4149
|
+
}).catch(console.error);
|
|
4150
|
+
return;
|
|
4151
|
+
}
|
|
4152
|
+
ensureFileStyles();
|
|
4153
|
+
const outer = document.createElement("div");
|
|
4154
|
+
outer.className = "fb-multi-outer fb-multi-has-files";
|
|
4155
|
+
const grid = document.createElement("div");
|
|
4156
|
+
grid.className = "fb-multi-grid fb-tiles-wrap";
|
|
4157
|
+
outer.appendChild(grid);
|
|
4158
|
+
const tile = buildPreviewTile(
|
|
4159
|
+
resourceId,
|
|
4160
|
+
state,
|
|
4161
|
+
Boolean(deps.onRemove),
|
|
4162
|
+
deps.onRemove ? () => deps.onRemove?.() : null,
|
|
4163
|
+
extras
|
|
4164
|
+
);
|
|
4165
|
+
grid.appendChild(tile);
|
|
4166
|
+
fileContainer.className = "file-preview-container";
|
|
4167
|
+
fileContainer.removeAttribute("style");
|
|
4168
|
+
while (fileContainer.firstChild)
|
|
4169
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
4170
|
+
fileContainer.appendChild(outer);
|
|
4171
|
+
}
|
|
4172
|
+
function buildMultiAddTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false) {
|
|
4173
|
+
const tile = document.createElement("div");
|
|
4174
|
+
tile.className = `fb-multi-add-tile fb-multi-add-tile-js${isDragOver ? " fb-drag-over-tile" : ""}`;
|
|
4175
|
+
const uploadBtn = document.createElement("button");
|
|
4176
|
+
uploadBtn.type = "button";
|
|
4177
|
+
uploadBtn.className = "fb-multi-add-upload fb-tile-add fb-file-dropzone";
|
|
4178
|
+
const cloudIcon = document.createElement("span");
|
|
4179
|
+
cloudIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
|
|
4180
|
+
cloudIcon.innerHTML = ICON_CLOUD;
|
|
4181
|
+
uploadBtn.appendChild(cloudIcon);
|
|
4182
|
+
const uploadLabel = document.createElement("span");
|
|
4183
|
+
uploadLabel.className = "fb-multi-add-label";
|
|
4184
|
+
uploadLabel.style.cssText = "font-size:11px;font-weight:600;";
|
|
4185
|
+
uploadLabel.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragTextMultiple", state);
|
|
4186
|
+
uploadBtn.appendChild(uploadLabel);
|
|
4187
|
+
uploadBtn.onclick = (e) => {
|
|
4188
|
+
e.stopPropagation();
|
|
4189
|
+
onUploadClick();
|
|
4190
|
+
};
|
|
4191
|
+
tile.appendChild(uploadBtn);
|
|
4192
|
+
if (hasLibrary && onLibraryClick) {
|
|
4193
|
+
const divider = document.createElement("div");
|
|
4194
|
+
divider.className = "fb-multi-add-divider";
|
|
4195
|
+
tile.appendChild(divider);
|
|
4196
|
+
const libBtn = document.createElement("button");
|
|
4197
|
+
libBtn.type = "button";
|
|
4198
|
+
libBtn.className = "fb-multi-add-library fb-tile-add-library fb-file-library-card";
|
|
4199
|
+
libBtn.setAttribute("aria-label", t("fromLibrary", state));
|
|
4200
|
+
const libIcon = document.createElement("span");
|
|
4201
|
+
libIcon.style.cssText = "width:14px;height:14px;display:block;flex-shrink:0;";
|
|
4202
|
+
libIcon.innerHTML = ICON_LIBRARY2;
|
|
4203
|
+
libBtn.appendChild(libIcon);
|
|
4204
|
+
libBtn.appendChild(document.createTextNode(t("fromLibrary", state)));
|
|
4205
|
+
libBtn.onclick = (e) => {
|
|
4206
|
+
e.stopPropagation();
|
|
4207
|
+
onLibraryClick();
|
|
4208
|
+
};
|
|
4209
|
+
tile.appendChild(libBtn);
|
|
4210
|
+
}
|
|
4211
|
+
return tile;
|
|
4212
|
+
}
|
|
4213
|
+
function buildPreviewTile(rid, state, canRemove, onRemove, extras) {
|
|
4214
|
+
ensureFileStyles();
|
|
4215
|
+
const meta = state.resourceIndex.get(rid);
|
|
4216
|
+
const tile = document.createElement("div");
|
|
4217
|
+
tile.className = "fb-preview-tile fb-checker fb-tile-resource resource-pill";
|
|
4218
|
+
tile.dataset.resourceId = rid;
|
|
4219
|
+
const actionsEl = createTileActions({
|
|
4220
|
+
canRemove: canRemove && onRemove !== null,
|
|
4221
|
+
removeHandler: onRemove,
|
|
4222
|
+
state,
|
|
4223
|
+
resourceId: rid,
|
|
4224
|
+
fileName: meta?.name ?? "",
|
|
4225
|
+
meta,
|
|
4226
|
+
replaceHandler: extras?.replaceHandler ?? null,
|
|
4227
|
+
libraryHandler: extras?.libraryHandler ?? null
|
|
4228
|
+
});
|
|
4229
|
+
fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
|
|
4230
|
+
console.error("Failed to render tile:", err);
|
|
4231
|
+
});
|
|
4232
|
+
return tile;
|
|
4233
|
+
}
|
|
4234
|
+
function buildPlaceholderTile(isDragOver = false) {
|
|
4235
|
+
const div = document.createElement("div");
|
|
4236
|
+
div.className = `fb-multi-placeholder fb-checker${isDragOver ? " fb-drag-over" : ""}`;
|
|
4237
|
+
return div;
|
|
4238
|
+
}
|
|
4239
|
+
function buildMetaLine(state, element, ridCount, maxCount, canClearAll, onClearAll) {
|
|
4240
|
+
const line = document.createElement("div");
|
|
4241
|
+
line.className = "fb-meta-line";
|
|
4242
|
+
const metaText = document.createElement("div");
|
|
4243
|
+
metaText.className = "fb-meta-text";
|
|
4244
|
+
if (element.maxSize && element.maxSize !== Infinity) {
|
|
4245
|
+
const sizeSpan = document.createElement("span");
|
|
4246
|
+
sizeSpan.textContent = t("hintMaxSize", state, { size: element.maxSize });
|
|
4247
|
+
metaText.appendChild(sizeSpan);
|
|
4248
|
+
metaText.appendChild(buildMetaDot());
|
|
4249
|
+
}
|
|
4250
|
+
const exts = getAllowedExtensions(
|
|
4251
|
+
element.accept
|
|
4252
|
+
);
|
|
4253
|
+
if (exts.length > 0) {
|
|
4254
|
+
const fmtSpan = document.createElement("span");
|
|
4255
|
+
fmtSpan.className = "fb-meta-mono";
|
|
4256
|
+
fmtSpan.textContent = exts.map((e) => e.toUpperCase()).join(", ");
|
|
4257
|
+
metaText.appendChild(fmtSpan);
|
|
4258
|
+
metaText.appendChild(buildMetaDot());
|
|
4259
|
+
}
|
|
4260
|
+
const countSpan = document.createElement("span");
|
|
4261
|
+
if (maxCount < Infinity) {
|
|
4262
|
+
countSpan.textContent = t("fileCountWithMax", state, {
|
|
4263
|
+
count: ridCount,
|
|
4264
|
+
max: maxCount
|
|
4265
|
+
});
|
|
3761
4266
|
} else {
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4267
|
+
const countKey = ridCount === 1 ? "fileCountSingle" : "fileCountPlural";
|
|
4268
|
+
countSpan.textContent = t(countKey, state, { count: ridCount });
|
|
4269
|
+
}
|
|
4270
|
+
metaText.appendChild(countSpan);
|
|
4271
|
+
line.appendChild(metaText);
|
|
4272
|
+
if (canClearAll && ridCount > 1) {
|
|
4273
|
+
const clearBtn = document.createElement("button");
|
|
4274
|
+
clearBtn.type = "button";
|
|
4275
|
+
clearBtn.className = "fb-clear-all-btn";
|
|
4276
|
+
clearBtn.textContent = t("clearAll", state);
|
|
4277
|
+
clearBtn.onclick = (e) => {
|
|
4278
|
+
e.stopPropagation();
|
|
4279
|
+
if (window.confirm(t("clearAll", state) + "?")) {
|
|
4280
|
+
onClearAll();
|
|
4281
|
+
}
|
|
4282
|
+
};
|
|
4283
|
+
line.appendChild(clearBtn);
|
|
3765
4284
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
4285
|
+
return line;
|
|
4286
|
+
}
|
|
4287
|
+
function buildMetaDot() {
|
|
4288
|
+
const dot = document.createElement("span");
|
|
4289
|
+
dot.className = "fb-meta-dot";
|
|
4290
|
+
return dot;
|
|
3768
4291
|
}
|
|
4292
|
+
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
3769
4293
|
function renderResourcePills(opts) {
|
|
3770
4294
|
const {
|
|
3771
4295
|
container,
|
|
3772
4296
|
rids,
|
|
3773
4297
|
state,
|
|
3774
4298
|
onRemove,
|
|
3775
|
-
hint,
|
|
3776
|
-
countInfo,
|
|
3777
4299
|
maxCount,
|
|
3778
4300
|
isReadonly = false,
|
|
3779
|
-
onLibraryPick
|
|
4301
|
+
onLibraryPick,
|
|
4302
|
+
element,
|
|
4303
|
+
onClearAll,
|
|
4304
|
+
openPicker: openPickerProp
|
|
3780
4305
|
} = opts;
|
|
3781
4306
|
ensureFileStyles();
|
|
3782
4307
|
const wrapper = container.closest("[data-files-wrapper]");
|
|
3783
4308
|
if (wrapper) {
|
|
3784
4309
|
wrapper.dataset.resourceIds = JSON.stringify(rids ?? []);
|
|
3785
4310
|
}
|
|
4311
|
+
const previousObserver = gridResizeObservers.get(container);
|
|
4312
|
+
if (previousObserver) {
|
|
4313
|
+
previousObserver.disconnect();
|
|
4314
|
+
gridResizeObservers.delete(container);
|
|
4315
|
+
}
|
|
3786
4316
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
3787
4317
|
const ridList = rids ?? [];
|
|
3788
|
-
const
|
|
4318
|
+
const effectiveMax = maxCount ?? Infinity;
|
|
4319
|
+
const atMax = effectiveMax !== Infinity && ridList.length >= effectiveMax;
|
|
3789
4320
|
const hasLibrary = !isReadonly && typeof onLibraryPick === "function";
|
|
3790
|
-
const
|
|
3791
|
-
const
|
|
3792
|
-
if (
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
const openPicker = () => {
|
|
3797
|
-
const picker = findFilePicker(container);
|
|
3798
|
-
if (picker) picker.click();
|
|
3799
|
-
};
|
|
3800
|
-
if (ridList.length === 0) {
|
|
3801
|
-
if (isReadonly) {
|
|
4321
|
+
const openPicker = openPickerProp ?? (() => {
|
|
4322
|
+
const pickerEl = container.closest("[data-files-wrapper]")?.querySelector('input[type="file"]');
|
|
4323
|
+
if (pickerEl) pickerEl.click();
|
|
4324
|
+
});
|
|
4325
|
+
if (isReadonly) {
|
|
4326
|
+
if (ridList.length === 0) {
|
|
3802
4327
|
const emptyEl = document.createElement("div");
|
|
3803
4328
|
emptyEl.className = "fb-tile-empty-text";
|
|
3804
4329
|
emptyEl.textContent = t("noFilesSelected", state);
|
|
3805
4330
|
container.appendChild(emptyEl);
|
|
3806
|
-
} else if (hasLibrary) {
|
|
3807
|
-
const row = document.createElement("div");
|
|
3808
|
-
row.className = "fb-file-card-row";
|
|
3809
|
-
const dropzone = buildEmptyDropzone(
|
|
3810
|
-
state,
|
|
3811
|
-
t("clickDragTextMultiple", state),
|
|
3812
|
-
buildSubHint(),
|
|
3813
|
-
openPicker
|
|
3814
|
-
);
|
|
3815
|
-
const libraryBtn = buildLibraryButton("card", state, onLibraryPick);
|
|
3816
|
-
row.appendChild(dropzone);
|
|
3817
|
-
row.appendChild(libraryBtn);
|
|
3818
|
-
container.appendChild(row);
|
|
3819
4331
|
} else {
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
4332
|
+
const grid2 = document.createElement("div");
|
|
4333
|
+
grid2.className = "fb-multi-readonly-grid";
|
|
4334
|
+
container.appendChild(grid2);
|
|
4335
|
+
for (const rid of ridList) {
|
|
4336
|
+
const meta = state.resourceIndex.get(rid);
|
|
4337
|
+
const tile = document.createElement("div");
|
|
4338
|
+
tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
|
|
4339
|
+
tile.dataset.resourceId = rid;
|
|
4340
|
+
const actionsEl = createTileActions({
|
|
4341
|
+
canRemove: false,
|
|
4342
|
+
removeHandler: null,
|
|
4343
|
+
state,
|
|
4344
|
+
resourceId: rid,
|
|
4345
|
+
fileName: meta?.name ?? "",
|
|
4346
|
+
meta
|
|
4347
|
+
});
|
|
4348
|
+
fillTileContent(tile, rid, meta, state, actionsEl).catch(console.error);
|
|
4349
|
+
tile.onclick = async () => {
|
|
4350
|
+
let url = null;
|
|
4351
|
+
if (state.config.getDownloadUrl) {
|
|
4352
|
+
url = state.config.getDownloadUrl(rid);
|
|
4353
|
+
} else if (state.config.getThumbnail) {
|
|
4354
|
+
url = await state.config.getThumbnail(rid);
|
|
4355
|
+
} else if (meta?.file instanceof File) {
|
|
4356
|
+
url = URL.createObjectURL(meta.file);
|
|
4357
|
+
}
|
|
4358
|
+
if (url) {
|
|
4359
|
+
window.open(url, "_blank");
|
|
4360
|
+
} else if (state.config.downloadFile) {
|
|
4361
|
+
state.config.downloadFile(rid, meta?.name ?? "");
|
|
4362
|
+
}
|
|
4363
|
+
};
|
|
4364
|
+
grid2.appendChild(tile);
|
|
4365
|
+
}
|
|
3827
4366
|
}
|
|
3828
4367
|
return;
|
|
3829
4368
|
}
|
|
3830
|
-
const
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
const
|
|
3839
|
-
|
|
3840
|
-
removeHandler: onRemove ? () => onRemove(rid) : null,
|
|
4369
|
+
const outerDiv = document.createElement("div");
|
|
4370
|
+
outerDiv.className = `fb-multi-outer${ridList.length > 0 ? " fb-multi-has-files" : ""}`;
|
|
4371
|
+
const grid = document.createElement("div");
|
|
4372
|
+
grid.className = "fb-multi-grid fb-tiles-wrap";
|
|
4373
|
+
outerDiv.appendChild(grid);
|
|
4374
|
+
container.appendChild(outerDiv);
|
|
4375
|
+
for (let i = 0; i < ridList.length; i++) {
|
|
4376
|
+
const rid = ridList[i];
|
|
4377
|
+
const tile = buildPreviewTile(
|
|
4378
|
+
rid,
|
|
3841
4379
|
state,
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
console.error("Failed to render tile:", err);
|
|
3847
|
-
});
|
|
3848
|
-
tilesWrap.appendChild(tile);
|
|
3849
|
-
}
|
|
3850
|
-
if (!isReadonly && !atMax) {
|
|
3851
|
-
const addTile = document.createElement("div");
|
|
3852
|
-
addTile.className = "fb-tile fb-tile-add";
|
|
3853
|
-
addTile.innerHTML = "+";
|
|
3854
|
-
addTile.onclick = openPicker;
|
|
3855
|
-
tilesWrap.appendChild(addTile);
|
|
3856
|
-
if (hasLibrary) {
|
|
3857
|
-
const libraryTile = buildLibraryButton("tile", state, onLibraryPick);
|
|
3858
|
-
tilesWrap.appendChild(libraryTile);
|
|
3859
|
-
}
|
|
3860
|
-
} else if (!isReadonly && atMax) {
|
|
3861
|
-
const chip = document.createElement("div");
|
|
3862
|
-
chip.className = "fb-tile-counter";
|
|
3863
|
-
chip.textContent = t("filesCounter", state, {
|
|
3864
|
-
count: ridList.length,
|
|
3865
|
-
max: maxCount
|
|
3866
|
-
});
|
|
3867
|
-
tilesWrap.appendChild(chip);
|
|
4380
|
+
onRemove !== null,
|
|
4381
|
+
onRemove ? () => onRemove(rid) : null
|
|
4382
|
+
);
|
|
4383
|
+
grid.appendChild(tile);
|
|
3868
4384
|
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4385
|
+
if (!atMax) {
|
|
4386
|
+
const addTile = buildMultiAddTile(
|
|
4387
|
+
state,
|
|
4388
|
+
hasLibrary,
|
|
4389
|
+
openPicker,
|
|
4390
|
+
onLibraryPick ?? null
|
|
4391
|
+
);
|
|
4392
|
+
grid.appendChild(addTile);
|
|
4393
|
+
}
|
|
4394
|
+
const occupied = ridList.length + (atMax ? 0 : 1);
|
|
4395
|
+
const adjustPlaceholders = () => {
|
|
4396
|
+
const tpl = getComputedStyle(grid).gridTemplateColumns;
|
|
4397
|
+
const cols = tpl ? tpl.split(" ").filter(Boolean).length : 0;
|
|
4398
|
+
if (!cols) return;
|
|
4399
|
+
const remainder = occupied % cols;
|
|
4400
|
+
const rowFill = remainder === 0 ? 0 : cols - remainder;
|
|
4401
|
+
const capacityRemaining = effectiveMax === Infinity ? rowFill : Math.max(0, effectiveMax - occupied);
|
|
4402
|
+
const needed = Math.min(rowFill, capacityRemaining);
|
|
4403
|
+
const existing = grid.querySelectorAll(".fb-multi-placeholder");
|
|
4404
|
+
if (existing.length > needed) {
|
|
4405
|
+
for (let i = existing.length - 1; i >= needed; i--) existing[i].remove();
|
|
4406
|
+
} else if (existing.length < needed) {
|
|
4407
|
+
for (let i = existing.length; i < needed; i++) {
|
|
4408
|
+
grid.appendChild(buildPlaceholderTile());
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
if (effectiveMax === Infinity || effectiveMax > occupied) {
|
|
4413
|
+
grid.appendChild(buildPlaceholderTile());
|
|
4414
|
+
}
|
|
4415
|
+
requestAnimationFrame(adjustPlaceholders);
|
|
4416
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
4417
|
+
const ro = new ResizeObserver(() => adjustPlaceholders());
|
|
4418
|
+
ro.observe(grid);
|
|
4419
|
+
gridResizeObservers.set(container, ro);
|
|
4420
|
+
}
|
|
4421
|
+
attachDragOverFeedback(outerDiv, {
|
|
4422
|
+
activeClass: "fb-drag-over",
|
|
4423
|
+
onEnter: () => {
|
|
4424
|
+
grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
|
|
4425
|
+
p.classList.add("fb-drag-over");
|
|
4426
|
+
});
|
|
4427
|
+
const addTile = grid.querySelector(".fb-multi-add-tile-js");
|
|
4428
|
+
if (addTile) {
|
|
4429
|
+
addTile.classList.add("fb-drag-over-tile");
|
|
4430
|
+
const label = addTile.querySelector(".fb-multi-add-label");
|
|
4431
|
+
if (label) label.textContent = t("dropToUpload", state);
|
|
4432
|
+
}
|
|
4433
|
+
},
|
|
4434
|
+
onLeave: () => {
|
|
4435
|
+
grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
|
|
4436
|
+
p.classList.remove("fb-drag-over");
|
|
4437
|
+
});
|
|
4438
|
+
const addTile = grid.querySelector(".fb-multi-add-tile-js");
|
|
4439
|
+
if (addTile) {
|
|
4440
|
+
addTile.classList.remove("fb-drag-over-tile");
|
|
4441
|
+
const label = addTile.querySelector(".fb-multi-add-label");
|
|
4442
|
+
if (label) label.textContent = t("clickDragTextMultiple", state);
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
});
|
|
4446
|
+
if (element) {
|
|
4447
|
+
const metaLine = buildMetaLine(
|
|
4448
|
+
state,
|
|
4449
|
+
element,
|
|
4450
|
+
ridList.length,
|
|
4451
|
+
effectiveMax,
|
|
4452
|
+
Boolean(onClearAll),
|
|
4453
|
+
onClearAll ?? (() => {
|
|
4454
|
+
})
|
|
4455
|
+
);
|
|
4456
|
+
container.appendChild(metaLine);
|
|
3876
4457
|
}
|
|
3877
4458
|
}
|
|
3878
4459
|
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3879
4460
|
const state = ctx.state;
|
|
3880
4461
|
const fileWrapper = document.createElement("div");
|
|
3881
4462
|
fileWrapper.className = "space-y-2";
|
|
4463
|
+
fileWrapper.dataset.filesWrapper = pathKey;
|
|
3882
4464
|
const picker = document.createElement("input");
|
|
3883
4465
|
picker.type = "file";
|
|
3884
4466
|
picker.name = pathKey;
|
|
3885
4467
|
picker.style.display = "none";
|
|
3886
|
-
|
|
3887
|
-
picker.accept = typeof element.accept === "string" ? element.accept : [
|
|
3888
|
-
...element.accept.extensions?.map((ext) => `.${ext}`) ?? [],
|
|
3889
|
-
...element.accept.mime ?? []
|
|
3890
|
-
].join(",") || "";
|
|
3891
|
-
}
|
|
4468
|
+
picker.accept = buildAcceptAttribute(element.accept);
|
|
3892
4469
|
const fileContainer = document.createElement("div");
|
|
3893
4470
|
fileContainer.className = "file-preview-container";
|
|
3894
4471
|
const initial = ctx.prefill[element.key];
|
|
@@ -3917,16 +4494,10 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3917
4494
|
setupDrop(container) {
|
|
3918
4495
|
setupDragAndDrop(container, handlers.dragHandler);
|
|
3919
4496
|
},
|
|
3920
|
-
restoreDropzone() {
|
|
3921
|
-
const hint = makeFieldHint(element, state);
|
|
3922
|
-
fileContainer.className = "file-preview-container w-full max-w-md bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
|
|
3923
|
-
fileContainer.style.height = "128px";
|
|
3924
|
-
setEmptyFileContainer(fileContainer, state, hint);
|
|
3925
|
-
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3926
|
-
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3927
|
-
},
|
|
3928
4497
|
onRemove() {
|
|
3929
|
-
const hiddenInput = fileWrapper.querySelector(
|
|
4498
|
+
const hiddenInput = fileWrapper.querySelector(
|
|
4499
|
+
'input[type="hidden"]'
|
|
4500
|
+
);
|
|
3930
4501
|
const currentRid = hiddenInput?.value;
|
|
3931
4502
|
if (currentRid) {
|
|
3932
4503
|
releaseLocalFileUrl(state.resourceIndex.get(currentRid)?.file);
|
|
@@ -3935,34 +4506,13 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3935
4506
|
renderEmptySingleState();
|
|
3936
4507
|
}
|
|
3937
4508
|
};
|
|
3938
|
-
const
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
const renderEmptySingleState = () => {
|
|
3946
|
-
if (state.config.pickExistingFiles && !element.disableLibrary) {
|
|
3947
|
-
fileContainer.className = "file-preview-container";
|
|
3948
|
-
fileContainer.removeAttribute("style");
|
|
3949
|
-
fileContainer.onclick = null;
|
|
3950
|
-
while (fileContainer.firstChild) {
|
|
3951
|
-
fileContainer.removeChild(fileContainer.firstChild);
|
|
3952
|
-
}
|
|
3953
|
-
const row = document.createElement("div");
|
|
3954
|
-
row.className = "fb-file-card-row";
|
|
3955
|
-
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
3956
|
-
const hint = makeFieldHint(element, state);
|
|
3957
|
-
const uploadCard = buildEmptyDropzone(
|
|
3958
|
-
state,
|
|
3959
|
-
t("clickDragText", state),
|
|
3960
|
-
hint,
|
|
3961
|
-
handlers.fileUploadHandler
|
|
3962
|
-
);
|
|
3963
|
-
uploadCard.style.cssText = "flex:1;min-width:0;height:128px;";
|
|
3964
|
-
setupDragAndDrop(uploadCard, handlers.dragHandler);
|
|
3965
|
-
const libraryBtn = buildLibraryButton("card", state, () => {
|
|
4509
|
+
const buildSingleExtras = () => {
|
|
4510
|
+
const hasLibrary = Boolean(
|
|
4511
|
+
state.config.pickExistingFiles && !element.disableLibrary
|
|
4512
|
+
);
|
|
4513
|
+
return {
|
|
4514
|
+
replaceHandler: state.config.uploadFile ? () => picker.click() : null,
|
|
4515
|
+
libraryHandler: hasLibrary ? () => {
|
|
3966
4516
|
handleLibraryPickSingle(
|
|
3967
4517
|
state,
|
|
3968
4518
|
element,
|
|
@@ -3971,20 +4521,54 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3971
4521
|
pathKey,
|
|
3972
4522
|
pathKey,
|
|
3973
4523
|
async (rid) => {
|
|
3974
|
-
|
|
4524
|
+
renderSingleFileFilled(
|
|
4525
|
+
fileContainer,
|
|
4526
|
+
rid,
|
|
4527
|
+
state,
|
|
4528
|
+
buildDeps(),
|
|
4529
|
+
buildSingleExtras()
|
|
4530
|
+
);
|
|
3975
4531
|
},
|
|
3976
4532
|
ctx.instance
|
|
3977
4533
|
).catch((err) => {
|
|
3978
4534
|
console.error("Library pick failed:", err);
|
|
3979
4535
|
});
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
4536
|
+
} : null
|
|
4537
|
+
};
|
|
4538
|
+
};
|
|
4539
|
+
const buildDeps = () => ({
|
|
4540
|
+
picker,
|
|
4541
|
+
fileUploadHandler: handlers.fileUploadHandler,
|
|
4542
|
+
dragHandler: handlers.dragHandler,
|
|
4543
|
+
setupDrop: handlers.setupDrop,
|
|
4544
|
+
onRemove: handlers.onRemove,
|
|
4545
|
+
onAfterUpload: (container, rid) => {
|
|
4546
|
+
renderSingleFileFilled(
|
|
4547
|
+
container,
|
|
4548
|
+
rid,
|
|
4549
|
+
state,
|
|
4550
|
+
buildDeps(),
|
|
4551
|
+
buildSingleExtras()
|
|
4552
|
+
);
|
|
3987
4553
|
}
|
|
4554
|
+
});
|
|
4555
|
+
const renderEmptySingleState = () => {
|
|
4556
|
+
ensureFileStyles();
|
|
4557
|
+
fileContainer.className = "file-preview-container";
|
|
4558
|
+
fileContainer.removeAttribute("style");
|
|
4559
|
+
while (fileContainer.firstChild)
|
|
4560
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
4561
|
+
const onLibraryClick = buildSingleExtras().libraryHandler;
|
|
4562
|
+
const wideTile = buildWideTile(
|
|
4563
|
+
state,
|
|
4564
|
+
onLibraryClick !== null,
|
|
4565
|
+
handlers.fileUploadHandler,
|
|
4566
|
+
onLibraryClick,
|
|
4567
|
+
false,
|
|
4568
|
+
makeFieldHint(element, state)
|
|
4569
|
+
);
|
|
4570
|
+
fileContainer.appendChild(wideTile);
|
|
4571
|
+
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3988
4572
|
};
|
|
3989
4573
|
if (initial) {
|
|
3990
4574
|
handleInitialFileData(
|
|
@@ -3993,11 +4577,11 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3993
4577
|
pathKey,
|
|
3994
4578
|
fileWrapper,
|
|
3995
4579
|
state,
|
|
3996
|
-
buildDeps()
|
|
4580
|
+
buildDeps(),
|
|
4581
|
+
buildSingleExtras()
|
|
3997
4582
|
);
|
|
3998
4583
|
const prefillMeta = state.resourceIndex.get(initial);
|
|
3999
4584
|
if (prefillMeta?.type?.startsWith("video/")) {
|
|
4000
|
-
fileContainer.onclick = handlers.fileUploadHandler;
|
|
4001
4585
|
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
4002
4586
|
}
|
|
4003
4587
|
} else {
|
|
@@ -4005,113 +4589,23 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4005
4589
|
}
|
|
4006
4590
|
picker.onchange = () => {
|
|
4007
4591
|
if (picker.files && picker.files.length > 0) {
|
|
4008
|
-
|
|
4009
|
-
file: picker.files[0],
|
|
4010
|
-
container: fileContainer,
|
|
4011
|
-
fieldName: pathKey,
|
|
4012
|
-
state,
|
|
4013
|
-
deps: buildDeps(),
|
|
4014
|
-
instance: ctx.instance,
|
|
4015
|
-
allowedExtensions: allowedExts,
|
|
4016
|
-
allowedMimes,
|
|
4017
|
-
maxSizeMB
|
|
4018
|
-
});
|
|
4592
|
+
handlers.dragHandler(picker.files);
|
|
4019
4593
|
}
|
|
4020
4594
|
};
|
|
4021
4595
|
fileWrapper.appendChild(fileContainer);
|
|
4022
4596
|
fileWrapper.appendChild(picker);
|
|
4023
4597
|
wrapper.appendChild(fileWrapper);
|
|
4024
4598
|
}
|
|
4025
|
-
function
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
filesPicker.name = pathKey;
|
|
4033
|
-
filesPicker.multiple = true;
|
|
4034
|
-
filesPicker.style.display = "none";
|
|
4035
|
-
if (element.accept) {
|
|
4036
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4037
|
-
...element.accept.extensions?.map((ext) => `.${ext}`) ?? [],
|
|
4038
|
-
...element.accept.mime ?? []
|
|
4039
|
-
].join(",") || "";
|
|
4040
|
-
}
|
|
4041
|
-
const filesContainer = document.createElement("div");
|
|
4042
|
-
filesContainer.className = "files-list-wrapper";
|
|
4043
|
-
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);";
|
|
4044
|
-
const list = document.createElement("div");
|
|
4045
|
-
list.className = "files-list";
|
|
4046
|
-
const initialFiles = ctx.prefill[element.key] || [];
|
|
4047
|
-
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
4048
|
-
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
4049
|
-
const filesFieldHint = makeFieldHint(element, state);
|
|
4050
|
-
const filesConstraints = {
|
|
4051
|
-
maxCount: Infinity,
|
|
4052
|
-
allowedExtensions: getAllowedExtensions(element.accept),
|
|
4053
|
-
allowedMimes: getAllowedMimes(element.accept),
|
|
4054
|
-
maxSize: element.maxSize ?? Infinity
|
|
4055
|
-
};
|
|
4056
|
-
filesContainer.appendChild(list);
|
|
4057
|
-
filesWrapper.appendChild(filesPicker);
|
|
4058
|
-
filesWrapper.appendChild(filesContainer);
|
|
4059
|
-
wrapper.appendChild(filesWrapper);
|
|
4060
|
-
const onLibraryPickFiles = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4061
|
-
handleLibraryPickMulti(
|
|
4062
|
-
state,
|
|
4063
|
-
element,
|
|
4064
|
-
filesWrapper,
|
|
4065
|
-
pathKey,
|
|
4066
|
-
initialFiles,
|
|
4067
|
-
Infinity,
|
|
4068
|
-
updateFilesList,
|
|
4069
|
-
ctx.instance
|
|
4070
|
-
).catch((err) => {
|
|
4071
|
-
console.error("Library pick failed:", err);
|
|
4072
|
-
});
|
|
4073
|
-
} : null;
|
|
4074
|
-
function updateFilesList() {
|
|
4075
|
-
const currentlyReadonly = isElementReadonly(element, state);
|
|
4076
|
-
renderResourcePills({
|
|
4077
|
-
container: list,
|
|
4078
|
-
rids: initialFiles,
|
|
4079
|
-
state,
|
|
4080
|
-
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
4081
|
-
releaseLocalFileUrl(state.resourceIndex.get(ridToRemove)?.file);
|
|
4082
|
-
const index = initialFiles.indexOf(ridToRemove);
|
|
4083
|
-
if (index > -1) initialFiles.splice(index, 1);
|
|
4084
|
-
updateFilesList();
|
|
4085
|
-
},
|
|
4086
|
-
hint: filesFieldHint,
|
|
4087
|
-
isReadonly: currentlyReadonly,
|
|
4088
|
-
onLibraryPick: currentlyReadonly ? null : onLibraryPickFiles
|
|
4089
|
-
});
|
|
4090
|
-
}
|
|
4091
|
-
updateFilesList();
|
|
4092
|
-
setupFilesDropHandler(
|
|
4093
|
-
filesContainer,
|
|
4094
|
-
initialFiles,
|
|
4095
|
-
state,
|
|
4096
|
-
updateFilesList,
|
|
4097
|
-
filesConstraints,
|
|
4098
|
-
pathKey,
|
|
4099
|
-
ctx.instance
|
|
4100
|
-
);
|
|
4101
|
-
setupFilesPickerHandler(
|
|
4102
|
-
filesPicker,
|
|
4103
|
-
initialFiles,
|
|
4104
|
-
state,
|
|
4105
|
-
updateFilesList,
|
|
4106
|
-
filesConstraints,
|
|
4107
|
-
pathKey,
|
|
4108
|
-
ctx.instance
|
|
4109
|
-
);
|
|
4599
|
+
function buildAcceptAttribute(accept) {
|
|
4600
|
+
if (!accept) return "";
|
|
4601
|
+
if (typeof accept === "string") return accept;
|
|
4602
|
+
return [
|
|
4603
|
+
...accept.extensions?.map((ext) => `.${ext}`) ?? [],
|
|
4604
|
+
...accept.mime ?? []
|
|
4605
|
+
].join(",");
|
|
4110
4606
|
}
|
|
4111
|
-
function
|
|
4607
|
+
function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
4112
4608
|
const state = ctx.state;
|
|
4113
|
-
const minFiles = element.minCount ?? 0;
|
|
4114
|
-
const maxFiles = element.maxCount ?? Infinity;
|
|
4115
4609
|
const filesWrapper = document.createElement("div");
|
|
4116
4610
|
filesWrapper.className = "space-y-2";
|
|
4117
4611
|
filesWrapper.dataset.filesWrapper = pathKey;
|
|
@@ -4120,15 +4614,9 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4120
4614
|
filesPicker.name = pathKey;
|
|
4121
4615
|
filesPicker.multiple = true;
|
|
4122
4616
|
filesPicker.style.display = "none";
|
|
4123
|
-
|
|
4124
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4125
|
-
...element.accept.extensions?.map((ext) => `.${ext}`) ?? [],
|
|
4126
|
-
...element.accept.mime ?? []
|
|
4127
|
-
].join(",") || "";
|
|
4128
|
-
}
|
|
4617
|
+
filesPicker.accept = buildAcceptAttribute(element.accept);
|
|
4129
4618
|
const filesContainer = document.createElement("div");
|
|
4130
4619
|
filesContainer.className = "files-list-wrapper";
|
|
4131
|
-
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);";
|
|
4132
4620
|
const list = document.createElement("div");
|
|
4133
4621
|
list.className = "files-list";
|
|
4134
4622
|
filesWrapper.appendChild(filesPicker);
|
|
@@ -4137,19 +4625,18 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4137
4625
|
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
4138
4626
|
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
4139
4627
|
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
4140
|
-
const
|
|
4141
|
-
const multipleConstraints = {
|
|
4628
|
+
const constraints = {
|
|
4142
4629
|
maxCount: maxFiles,
|
|
4143
4630
|
allowedExtensions: getAllowedExtensions(element.accept),
|
|
4144
4631
|
allowedMimes: getAllowedMimes(element.accept),
|
|
4145
|
-
|
|
4632
|
+
// Prefer schema's `maxSize`; fall back to legacy `maxSizeMB` for
|
|
4633
|
+
// backward compatibility (matches addFileSizeHint in validation.ts).
|
|
4634
|
+
maxSize: element.maxSize ?? element.maxSizeMB ?? Infinity
|
|
4146
4635
|
};
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
4150
|
-
return countText + minMaxText;
|
|
4636
|
+
const openPicker = () => {
|
|
4637
|
+
filesPicker.click();
|
|
4151
4638
|
};
|
|
4152
|
-
const
|
|
4639
|
+
const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4153
4640
|
handleLibraryPickMulti(
|
|
4154
4641
|
state,
|
|
4155
4642
|
element,
|
|
@@ -4163,30 +4650,35 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4163
4650
|
console.error("Library pick failed:", err);
|
|
4164
4651
|
});
|
|
4165
4652
|
} : null;
|
|
4166
|
-
|
|
4653
|
+
function updateFilesDisplay() {
|
|
4167
4654
|
const currentlyReadonly = isElementReadonly(element, state);
|
|
4168
4655
|
renderResourcePills({
|
|
4169
4656
|
container: list,
|
|
4170
4657
|
rids: initialFiles,
|
|
4171
4658
|
state,
|
|
4172
|
-
onRemove: currentlyReadonly ? null : (
|
|
4173
|
-
releaseLocalFileUrl(state.resourceIndex.get(
|
|
4174
|
-
initialFiles.
|
|
4659
|
+
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
4660
|
+
releaseLocalFileUrl(state.resourceIndex.get(ridToRemove)?.file);
|
|
4661
|
+
const index = initialFiles.indexOf(ridToRemove);
|
|
4662
|
+
if (index > -1) initialFiles.splice(index, 1);
|
|
4175
4663
|
updateFilesDisplay();
|
|
4176
4664
|
},
|
|
4177
|
-
hint: multipleFilesHint,
|
|
4178
|
-
countInfo: buildCountInfo(),
|
|
4179
4665
|
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
4180
4666
|
isReadonly: currentlyReadonly,
|
|
4181
|
-
onLibraryPick: currentlyReadonly ? null :
|
|
4667
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPick,
|
|
4668
|
+
element,
|
|
4669
|
+
onClearAll: currentlyReadonly ? void 0 : () => {
|
|
4670
|
+
initialFiles.splice(0);
|
|
4671
|
+
updateFilesDisplay();
|
|
4672
|
+
},
|
|
4673
|
+
openPicker
|
|
4182
4674
|
});
|
|
4183
|
-
}
|
|
4675
|
+
}
|
|
4184
4676
|
setupFilesDropHandler(
|
|
4185
4677
|
filesContainer,
|
|
4186
4678
|
initialFiles,
|
|
4187
4679
|
state,
|
|
4188
4680
|
updateFilesDisplay,
|
|
4189
|
-
|
|
4681
|
+
constraints,
|
|
4190
4682
|
pathKey,
|
|
4191
4683
|
ctx.instance
|
|
4192
4684
|
);
|
|
@@ -4195,13 +4687,25 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
4195
4687
|
initialFiles,
|
|
4196
4688
|
state,
|
|
4197
4689
|
updateFilesDisplay,
|
|
4198
|
-
|
|
4690
|
+
constraints,
|
|
4199
4691
|
pathKey,
|
|
4200
4692
|
ctx.instance
|
|
4201
4693
|
);
|
|
4202
4694
|
updateFilesDisplay();
|
|
4203
4695
|
wrapper.appendChild(filesWrapper);
|
|
4204
4696
|
}
|
|
4697
|
+
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
4698
|
+
setupMultiFileEditMode(element, ctx, wrapper, pathKey, Infinity);
|
|
4699
|
+
}
|
|
4700
|
+
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
4701
|
+
setupMultiFileEditMode(
|
|
4702
|
+
element,
|
|
4703
|
+
ctx,
|
|
4704
|
+
wrapper,
|
|
4705
|
+
pathKey,
|
|
4706
|
+
element.maxCount ?? Infinity
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4205
4709
|
|
|
4206
4710
|
// src/components/file/validate.ts
|
|
4207
4711
|
function readMultiFileResourceIds(scopeRoot, fullKey) {
|
|
@@ -4324,33 +4828,36 @@ function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
|
4324
4828
|
hiddenInput.name = pathKey;
|
|
4325
4829
|
hiddenInput.value = initial;
|
|
4326
4830
|
wrapper.appendChild(hiddenInput);
|
|
4327
|
-
renderFilePreviewReadonly(initial, state).then((
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4831
|
+
renderFilePreviewReadonly(initial, state).then((tile) => {
|
|
4832
|
+
tile.classList.add(
|
|
4833
|
+
"fb-single-readonly-filled",
|
|
4834
|
+
"fb-readonly-tile",
|
|
4835
|
+
"fb-checker"
|
|
4836
|
+
);
|
|
4837
|
+
wrapper.appendChild(tile);
|
|
4838
|
+
}).catch(console.error);
|
|
4333
4839
|
} else {
|
|
4334
4840
|
wrapper.appendChild(buildEmptyReadonlyTile(state));
|
|
4335
4841
|
}
|
|
4336
4842
|
}
|
|
4337
4843
|
function buildEmptyReadonlyTile(state) {
|
|
4844
|
+
ensureFileStyles();
|
|
4338
4845
|
const emptyState = document.createElement("div");
|
|
4339
4846
|
emptyState.style.cssText = `
|
|
4340
|
-
|
|
4341
|
-
height:${TILE_SIZE};
|
|
4847
|
+
height: 220px;
|
|
4342
4848
|
display:flex;
|
|
4343
4849
|
align-items:center;
|
|
4344
4850
|
justify-content:center;
|
|
4345
|
-
background:
|
|
4346
|
-
border-radius:
|
|
4347
|
-
border:1px solid
|
|
4851
|
+
background: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
|
|
4852
|
+
border-radius:0.75rem;
|
|
4853
|
+
border:1px solid #e2e8f0;
|
|
4348
4854
|
`;
|
|
4349
4855
|
emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
4350
4856
|
return emptyState;
|
|
4351
4857
|
}
|
|
4352
|
-
function renderMultiFileReadonly(rids, state, wrapper, pathKey,
|
|
4858
|
+
function renderMultiFileReadonly(rids, state, wrapper, pathKey, _marginTop) {
|
|
4353
4859
|
addPrefillFilesToIndex(rids, state.resourceIndex);
|
|
4860
|
+
ensureFileStyles();
|
|
4354
4861
|
const filesWrapper = document.createElement("div");
|
|
4355
4862
|
filesWrapper.dataset.filesWrapper = pathKey;
|
|
4356
4863
|
filesWrapper.dataset.resourceIds = JSON.stringify(rids);
|
|
@@ -4362,22 +4869,32 @@ function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
|
|
|
4362
4869
|
filesWrapper.appendChild(emptyEl);
|
|
4363
4870
|
return;
|
|
4364
4871
|
}
|
|
4365
|
-
const
|
|
4366
|
-
|
|
4367
|
-
filesWrapper.appendChild(
|
|
4872
|
+
const grid = document.createElement("div");
|
|
4873
|
+
grid.className = "fb-multi-readonly-grid";
|
|
4874
|
+
filesWrapper.appendChild(grid);
|
|
4368
4875
|
const placeholders = rids.map(() => {
|
|
4369
|
-
const
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
return
|
|
4876
|
+
const ph = document.createElement("div");
|
|
4877
|
+
ph.className = "fb-readonly-tile fb-checker fb-tile";
|
|
4878
|
+
grid.appendChild(ph);
|
|
4879
|
+
return ph;
|
|
4373
4880
|
});
|
|
4374
4881
|
for (let i = 0; i < rids.length; i++) {
|
|
4375
4882
|
const resourceId = rids[i];
|
|
4376
4883
|
const placeholder = placeholders[i];
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4884
|
+
const meta = state.resourceIndex.get(resourceId);
|
|
4885
|
+
renderFilePreviewReadonly(resourceId, state, meta?.name).then((tile) => {
|
|
4886
|
+
tile.classList.add(
|
|
4887
|
+
"fb-readonly-tile",
|
|
4888
|
+
"fb-checker",
|
|
4889
|
+
"fb-tile-resource"
|
|
4890
|
+
);
|
|
4891
|
+
tile.dataset.resourceId = resourceId;
|
|
4892
|
+
placeholder.replaceWith(tile);
|
|
4893
|
+
}).catch(() => {
|
|
4894
|
+
const tile = document.createElement("div");
|
|
4895
|
+
tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
|
|
4896
|
+
tile.dataset.resourceId = resourceId;
|
|
4897
|
+
placeholder.replaceWith(tile);
|
|
4381
4898
|
});
|
|
4382
4899
|
}
|
|
4383
4900
|
}
|
|
@@ -4389,7 +4906,7 @@ function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
|
|
|
4389
4906
|
function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
4390
4907
|
const rawPrefill = ctx.prefill[element.key];
|
|
4391
4908
|
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
4392
|
-
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey
|
|
4909
|
+
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
|
|
4393
4910
|
}
|
|
4394
4911
|
|
|
4395
4912
|
// src/components/file.ts
|
|
@@ -4720,51 +5237,25 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
4720
5237
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
4721
5238
|
});
|
|
4722
5239
|
}
|
|
4723
|
-
let
|
|
4724
|
-
let countDisplay = null;
|
|
5240
|
+
let addUpdate = null;
|
|
4725
5241
|
if (!readonly) {
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
4741
|
-
});
|
|
4742
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
4743
|
-
addBtn.style.backgroundColor = "transparent";
|
|
4744
|
-
});
|
|
4745
|
-
addBtn.onclick = () => {
|
|
4746
|
-
const defaultColour = element.default || "#000000";
|
|
4747
|
-
values.push(defaultColour);
|
|
4748
|
-
addColourItem(defaultColour);
|
|
4749
|
-
updateAddButton();
|
|
4750
|
-
updateRemoveButtons();
|
|
4751
|
-
};
|
|
4752
|
-
countDisplay = document.createElement("span");
|
|
4753
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
4754
|
-
addRow.appendChild(addBtn);
|
|
4755
|
-
addRow.appendChild(countDisplay);
|
|
4756
|
-
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);
|
|
4757
5256
|
}
|
|
4758
5257
|
function updateAddButton() {
|
|
4759
|
-
if (
|
|
4760
|
-
const addBtn = addRow.querySelector(".add-colour-btn");
|
|
4761
|
-
if (addBtn) {
|
|
4762
|
-
const disabled = values.length >= maxCount;
|
|
4763
|
-
addBtn.disabled = disabled;
|
|
4764
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
4765
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
4766
|
-
}
|
|
4767
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
5258
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
4768
5259
|
}
|
|
4769
5260
|
values.forEach((value) => addColourItem(value));
|
|
4770
5261
|
updateAddButton();
|
|
@@ -5200,50 +5691,24 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
5200
5691
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5201
5692
|
});
|
|
5202
5693
|
}
|
|
5203
|
-
let
|
|
5204
|
-
let countDisplay = null;
|
|
5694
|
+
let addUpdate = null;
|
|
5205
5695
|
if (!readonly) {
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
5220
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5221
|
-
});
|
|
5222
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
5223
|
-
addBtn.style.backgroundColor = "transparent";
|
|
5224
|
-
});
|
|
5225
|
-
addBtn.onclick = () => {
|
|
5226
|
-
values.push(defaultValue);
|
|
5227
|
-
addSliderItem(defaultValue);
|
|
5228
|
-
updateAddButton();
|
|
5229
|
-
updateRemoveButtons();
|
|
5230
|
-
};
|
|
5231
|
-
countDisplay = document.createElement("span");
|
|
5232
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
5233
|
-
addRow.appendChild(addBtn);
|
|
5234
|
-
addRow.appendChild(countDisplay);
|
|
5235
|
-
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);
|
|
5236
5709
|
}
|
|
5237
5710
|
function updateAddButton() {
|
|
5238
|
-
if (
|
|
5239
|
-
const addBtn = addRow.querySelector(".add-slider-btn");
|
|
5240
|
-
if (addBtn) {
|
|
5241
|
-
const disabled = values.length >= maxCount;
|
|
5242
|
-
addBtn.disabled = disabled;
|
|
5243
|
-
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
5244
|
-
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
5245
|
-
}
|
|
5246
|
-
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
5711
|
+
if (addUpdate) addUpdate(values.length, maxCount);
|
|
5247
5712
|
}
|
|
5248
5713
|
values.forEach((value) => addSliderItem(value));
|
|
5249
5714
|
updateAddButton();
|
|
@@ -5527,7 +5992,7 @@ function createPrefillHints(element, pathKey) {
|
|
|
5527
5992
|
return null;
|
|
5528
5993
|
}
|
|
5529
5994
|
const hintsContainer = document.createElement("div");
|
|
5530
|
-
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-
|
|
5995
|
+
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
|
|
5531
5996
|
element.prefillHints.forEach((hint, index) => {
|
|
5532
5997
|
const hintButton = document.createElement("button");
|
|
5533
5998
|
hintButton.type = "button";
|
|
@@ -5542,14 +6007,14 @@ function createPrefillHints(element, pathKey) {
|
|
|
5542
6007
|
}
|
|
5543
6008
|
function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
5544
6009
|
const containerWrap = document.createElement("div");
|
|
5545
|
-
containerWrap.className = "border border-gray-200 rounded-lg p-
|
|
6010
|
+
containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
|
|
5546
6011
|
containerWrap.setAttribute("data-container", pathKey);
|
|
5547
6012
|
const itemsWrap = document.createElement("div");
|
|
5548
6013
|
const columns = element.columns || 1;
|
|
5549
6014
|
if (columns === 1) {
|
|
5550
|
-
itemsWrap.className = "space-y-
|
|
6015
|
+
itemsWrap.className = "space-y-2";
|
|
5551
6016
|
} else {
|
|
5552
|
-
itemsWrap.className = `grid grid-cols-${columns} gap-
|
|
6017
|
+
itemsWrap.className = `grid grid-cols-${columns} gap-2`;
|
|
5553
6018
|
}
|
|
5554
6019
|
const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
|
|
5555
6020
|
if (!containerIsReadonly) {
|
|
@@ -5583,16 +6048,70 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
5583
6048
|
containerWrap.appendChild(itemsWrap);
|
|
5584
6049
|
wrapper.appendChild(containerWrap);
|
|
5585
6050
|
}
|
|
6051
|
+
function getChildWrapperClass(isSlides, columns) {
|
|
6052
|
+
if (isSlides) {
|
|
6053
|
+
return "space-y-2";
|
|
6054
|
+
}
|
|
6055
|
+
const cols = columns || 1;
|
|
6056
|
+
return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
|
|
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
|
+
}
|
|
5586
6099
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
5587
6100
|
const state = ctx.state;
|
|
5588
6101
|
const containerIsReadonly = isElementReadonly(element, state, ctx);
|
|
5589
6102
|
const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
|
|
5590
6103
|
const containerWrap = document.createElement("div");
|
|
5591
|
-
containerWrap.className = "border border-gray-200 rounded-lg p-
|
|
5592
|
-
const countDisplay = document.createElement("span");
|
|
5593
|
-
countDisplay.className = "text-sm text-gray-500";
|
|
6104
|
+
containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
|
|
5594
6105
|
const itemsWrap = document.createElement("div");
|
|
5595
|
-
|
|
6106
|
+
const isSlides = element.displayMode === "slides";
|
|
6107
|
+
if (isSlides) {
|
|
6108
|
+
itemsWrap.className = "fb-container-slides";
|
|
6109
|
+
const slideCols = element.columns;
|
|
6110
|
+
const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
|
|
6111
|
+
itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:8px;align-items:start;`;
|
|
6112
|
+
} else {
|
|
6113
|
+
itemsWrap.className = "space-y-2";
|
|
6114
|
+
}
|
|
5596
6115
|
if (!containerIsReadonly) {
|
|
5597
6116
|
const hintsElement = createPrefillHints(element, element.key);
|
|
5598
6117
|
if (hintsElement) {
|
|
@@ -5604,97 +6123,64 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5604
6123
|
const pre = Array.isArray(ctx.prefill?.[element.key]) ? ctx.prefill[element.key] : null;
|
|
5605
6124
|
const childDefaults = extractChildDefaults(element.elements);
|
|
5606
6125
|
const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
|
|
5607
|
-
const
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
add.addEventListener("mouseenter", () => {
|
|
5620
|
-
add.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5621
|
-
});
|
|
5622
|
-
add.addEventListener("mouseleave", () => {
|
|
5623
|
-
add.style.backgroundColor = "transparent";
|
|
5624
|
-
});
|
|
5625
|
-
add.onclick = () => {
|
|
5626
|
-
if (countItems() < max) {
|
|
5627
|
-
const idx = countItems();
|
|
5628
|
-
const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
|
|
5629
|
-
const subCtx = {
|
|
5630
|
-
state: ctx.state,
|
|
5631
|
-
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
5632
|
-
prefill: childDefaults,
|
|
5633
|
-
// Defaults for enableIf evaluation
|
|
5634
|
-
formData: currentFormData,
|
|
5635
|
-
// Current root data from DOM for enableIf
|
|
5636
|
-
inheritedReadonly: childInheritedReadonly
|
|
5637
|
-
};
|
|
5638
|
-
const item = document.createElement("div");
|
|
5639
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
5640
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
5641
|
-
const childWrapper = document.createElement("div");
|
|
5642
|
-
const columns = element.columns || 1;
|
|
5643
|
-
if (columns === 1) {
|
|
5644
|
-
childWrapper.className = "space-y-4";
|
|
5645
|
-
} else {
|
|
5646
|
-
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5647
|
-
}
|
|
5648
|
-
element.elements.forEach((child) => {
|
|
5649
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5650
|
-
childWrapper.appendChild(
|
|
5651
|
-
createHiddenInput(
|
|
5652
|
-
pathJoin(subCtx.path, child.key),
|
|
5653
|
-
("default" in child ? child.default : null) ?? null
|
|
5654
|
-
)
|
|
5655
|
-
);
|
|
5656
|
-
} else {
|
|
5657
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
5658
|
-
}
|
|
5659
|
-
});
|
|
5660
|
-
item.appendChild(childWrapper);
|
|
5661
|
-
if (!containerIsReadonly) {
|
|
5662
|
-
const rem = document.createElement("button");
|
|
5663
|
-
rem.type = "button";
|
|
5664
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
5665
|
-
rem.style.cssText = `
|
|
5666
|
-
color: var(--fb-error-color);
|
|
5667
|
-
background-color: transparent;
|
|
5668
|
-
transition: background-color var(--fb-transition-duration);
|
|
5669
|
-
`;
|
|
5670
|
-
rem.textContent = "\u2715";
|
|
5671
|
-
rem.addEventListener("mouseenter", () => {
|
|
5672
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5673
|
-
});
|
|
5674
|
-
rem.addEventListener("mouseleave", () => {
|
|
5675
|
-
rem.style.backgroundColor = "transparent";
|
|
5676
|
-
});
|
|
5677
|
-
rem.onclick = () => handleRemoveItem(item);
|
|
5678
|
-
item.style.position = "relative";
|
|
5679
|
-
item.appendChild(rem);
|
|
5680
|
-
}
|
|
5681
|
-
itemsWrap.appendChild(item);
|
|
5682
|
-
updateAddButton();
|
|
5683
|
-
}
|
|
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
|
|
5684
6138
|
};
|
|
5685
|
-
|
|
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();
|
|
5686
6166
|
};
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
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"
|
|
5691
6174
|
);
|
|
5692
|
-
if (
|
|
5693
|
-
|
|
5694
|
-
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
5695
|
-
existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
|
|
6175
|
+
if (firstSlide && firstSlide.offsetHeight > 0) {
|
|
6176
|
+
slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
|
|
5696
6177
|
}
|
|
5697
|
-
|
|
6178
|
+
};
|
|
6179
|
+
const updateAddButton = () => {
|
|
6180
|
+
const currentCount = countItems();
|
|
6181
|
+
if (slideAddUpdate) slideAddUpdate(currentCount, max);
|
|
6182
|
+
if (pillAddUpdate) pillAddUpdate(currentCount, max);
|
|
6183
|
+
if (slideAddTile) syncSlideTileSize();
|
|
5698
6184
|
};
|
|
5699
6185
|
const handleRemoveItem = (item) => {
|
|
5700
6186
|
item.remove();
|
|
@@ -5713,14 +6199,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5713
6199
|
inheritedReadonly: childInheritedReadonly
|
|
5714
6200
|
};
|
|
5715
6201
|
const item = document.createElement("div");
|
|
5716
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-
|
|
6202
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
5717
6203
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
5718
6204
|
const childWrapper = document.createElement("div");
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
childWrapper.className = "space-y-4";
|
|
6205
|
+
if (isSlides) {
|
|
6206
|
+
childWrapper.className = "space-y-2";
|
|
5722
6207
|
} else {
|
|
5723
|
-
|
|
6208
|
+
const columns = element.columns || 1;
|
|
6209
|
+
if (columns === 1) {
|
|
6210
|
+
childWrapper.className = "space-y-2";
|
|
6211
|
+
} else {
|
|
6212
|
+
childWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
6213
|
+
}
|
|
5724
6214
|
}
|
|
5725
6215
|
element.elements.forEach((child) => {
|
|
5726
6216
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
@@ -5734,24 +6224,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5734
6224
|
});
|
|
5735
6225
|
item.appendChild(childWrapper);
|
|
5736
6226
|
if (!containerIsReadonly) {
|
|
5737
|
-
|
|
5738
|
-
rem.type = "button";
|
|
5739
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
5740
|
-
rem.style.cssText = `
|
|
5741
|
-
color: var(--fb-error-color);
|
|
5742
|
-
background-color: transparent;
|
|
5743
|
-
transition: background-color var(--fb-transition-duration);
|
|
5744
|
-
`;
|
|
5745
|
-
rem.textContent = "\u2715";
|
|
5746
|
-
rem.addEventListener("mouseenter", () => {
|
|
5747
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5748
|
-
});
|
|
5749
|
-
rem.addEventListener("mouseleave", () => {
|
|
5750
|
-
rem.style.backgroundColor = "transparent";
|
|
5751
|
-
});
|
|
5752
|
-
rem.onclick = () => handleRemoveItem(item);
|
|
5753
|
-
item.style.position = "relative";
|
|
5754
|
-
item.appendChild(rem);
|
|
6227
|
+
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
5755
6228
|
}
|
|
5756
6229
|
itemsWrap.appendChild(item);
|
|
5757
6230
|
});
|
|
@@ -5769,14 +6242,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5769
6242
|
inheritedReadonly: childInheritedReadonly
|
|
5770
6243
|
};
|
|
5771
6244
|
const item = document.createElement("div");
|
|
5772
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-
|
|
6245
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
5773
6246
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
5774
6247
|
const childWrapper = document.createElement("div");
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
childWrapper.className = "space-y-4";
|
|
6248
|
+
if (isSlides) {
|
|
6249
|
+
childWrapper.className = "space-y-2";
|
|
5778
6250
|
} else {
|
|
5779
|
-
|
|
6251
|
+
const columns = element.columns || 1;
|
|
6252
|
+
if (columns === 1) {
|
|
6253
|
+
childWrapper.className = "space-y-2";
|
|
6254
|
+
} else {
|
|
6255
|
+
childWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
6256
|
+
}
|
|
5780
6257
|
}
|
|
5781
6258
|
element.elements.forEach((child) => {
|
|
5782
6259
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
@@ -5791,41 +6268,43 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5791
6268
|
}
|
|
5792
6269
|
});
|
|
5793
6270
|
item.appendChild(childWrapper);
|
|
5794
|
-
|
|
5795
|
-
rem.type = "button";
|
|
5796
|
-
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
5797
|
-
rem.style.cssText = `
|
|
5798
|
-
color: var(--fb-error-color);
|
|
5799
|
-
background-color: transparent;
|
|
5800
|
-
transition: background-color var(--fb-transition-duration);
|
|
5801
|
-
`;
|
|
5802
|
-
rem.textContent = "\u2715";
|
|
5803
|
-
rem.addEventListener("mouseenter", () => {
|
|
5804
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5805
|
-
});
|
|
5806
|
-
rem.addEventListener("mouseleave", () => {
|
|
5807
|
-
rem.style.backgroundColor = "transparent";
|
|
5808
|
-
});
|
|
5809
|
-
rem.onclick = () => {
|
|
6271
|
+
mountRemoveButton(item, () => {
|
|
5810
6272
|
if (countItems() > min) {
|
|
5811
6273
|
handleRemoveItem(item);
|
|
5812
6274
|
}
|
|
5813
|
-
};
|
|
5814
|
-
item.style.position = "relative";
|
|
5815
|
-
item.appendChild(rem);
|
|
6275
|
+
});
|
|
5816
6276
|
itemsWrap.appendChild(item);
|
|
5817
6277
|
}
|
|
5818
6278
|
}
|
|
5819
6279
|
containerWrap.appendChild(itemsWrap);
|
|
5820
6280
|
if (!containerIsReadonly) {
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
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
|
+
}
|
|
5826
6298
|
}
|
|
5827
6299
|
updateAddButton();
|
|
5828
6300
|
wrapper.appendChild(containerWrap);
|
|
6301
|
+
if (slideAddTile) {
|
|
6302
|
+
if (typeof requestAnimationFrame === "function") {
|
|
6303
|
+
requestAnimationFrame(syncSlideTileSize);
|
|
6304
|
+
} else {
|
|
6305
|
+
syncSlideTileSize();
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
5829
6308
|
}
|
|
5830
6309
|
var validateElementFunc = null;
|
|
5831
6310
|
function setValidateElement(fn) {
|
|
@@ -7804,7 +8283,7 @@ function filterFilesForDropdown(query, files, labels) {
|
|
|
7804
8283
|
});
|
|
7805
8284
|
}
|
|
7806
8285
|
var TEXTAREA_FONT = "font-size: var(--fb-font-size, 14px); font-family: var(--fb-font-family, inherit); line-height: 1.6;";
|
|
7807
|
-
var TEXTAREA_PADDING = "padding:
|
|
8286
|
+
var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
|
|
7808
8287
|
function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
7809
8288
|
const state = ctx.state;
|
|
7810
8289
|
const files = [...initialValue.files];
|
|
@@ -7851,7 +8330,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
7851
8330
|
});
|
|
7852
8331
|
const errorEl = document.createElement("div");
|
|
7853
8332
|
errorEl.className = "fb-richinput-error";
|
|
7854
|
-
errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px
|
|
8333
|
+
errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px 10px 6px;";
|
|
7855
8334
|
let errorTimer = null;
|
|
7856
8335
|
function showUploadError(message) {
|
|
7857
8336
|
errorEl.textContent = message;
|
|
@@ -7927,7 +8406,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
7927
8406
|
});
|
|
7928
8407
|
const filesRow = document.createElement("div");
|
|
7929
8408
|
filesRow.className = "fb-richinput-files";
|
|
7930
|
-
filesRow.style.cssText = "display: none; flex-wrap: wrap; gap:
|
|
8409
|
+
filesRow.style.cssText = "display: none; flex-wrap: wrap; gap: 4px; padding: 6px 10px 0; align-items: center;";
|
|
7931
8410
|
const fileInput = document.createElement("input");
|
|
7932
8411
|
fileInput.type = "file";
|
|
7933
8412
|
fileInput.multiple = true;
|
|
@@ -8057,13 +8536,13 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
8057
8536
|
paperclipBtn.title = t("richinputAttachFile", state);
|
|
8058
8537
|
paperclipBtn.style.cssText = `
|
|
8059
8538
|
position: absolute;
|
|
8060
|
-
right:
|
|
8061
|
-
bottom:
|
|
8539
|
+
right: 6px;
|
|
8540
|
+
bottom: 6px;
|
|
8062
8541
|
z-index: 2;
|
|
8063
|
-
width:
|
|
8064
|
-
height:
|
|
8542
|
+
width: 28px;
|
|
8543
|
+
height: 28px;
|
|
8065
8544
|
border: none;
|
|
8066
|
-
border-radius:
|
|
8545
|
+
border-radius: 6px;
|
|
8067
8546
|
background: transparent;
|
|
8068
8547
|
cursor: pointer;
|
|
8069
8548
|
display: flex;
|
|
@@ -8417,7 +8896,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
8417
8896
|
outerDiv.appendChild(errorEl);
|
|
8418
8897
|
if (element.minLength != null || element.maxLength != null) {
|
|
8419
8898
|
const counterRow = document.createElement("div");
|
|
8420
|
-
counterRow.style.cssText = "position: relative; padding: 2px
|
|
8899
|
+
counterRow.style.cssText = "position: relative; padding: 2px 10px 4px; text-align: right;";
|
|
8421
8900
|
const counter = createCharCounter(element, textarea, false);
|
|
8422
8901
|
counter.style.cssText = `
|
|
8423
8902
|
position: static;
|
|
@@ -8720,10 +9199,7 @@ var TAGS = {
|
|
|
8720
9199
|
"-": ["<hr />"]
|
|
8721
9200
|
};
|
|
8722
9201
|
function outdent(str) {
|
|
8723
|
-
return str.replace(
|
|
8724
|
-
RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
|
|
8725
|
-
""
|
|
8726
|
-
);
|
|
9202
|
+
return str.replace(RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"), "");
|
|
8727
9203
|
}
|
|
8728
9204
|
function encodeAttr(str) {
|
|
8729
9205
|
return (str + "").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -8886,12 +9362,7 @@ function ensureMarkdownStyles() {
|
|
|
8886
9362
|
`;
|
|
8887
9363
|
document.head.appendChild(style);
|
|
8888
9364
|
}
|
|
8889
|
-
var ANCHOR_DANGEROUS_SCHEMES = [
|
|
8890
|
-
"javascript:",
|
|
8891
|
-
"data:",
|
|
8892
|
-
"vbscript:",
|
|
8893
|
-
"blob:"
|
|
8894
|
-
];
|
|
9365
|
+
var ANCHOR_DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:", "blob:"];
|
|
8895
9366
|
var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
|
|
8896
9367
|
function isImgSrcDangerous(normalized) {
|
|
8897
9368
|
if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
|
|
@@ -9211,7 +9682,8 @@ function createInfoButton(element) {
|
|
|
9211
9682
|
}
|
|
9212
9683
|
function createLabelContainer(element) {
|
|
9213
9684
|
const label = document.createElement("div");
|
|
9214
|
-
label.className = "flex items-center mb-
|
|
9685
|
+
label.className = "flex items-center mb-1";
|
|
9686
|
+
label.dataset.fbLabelRow = "";
|
|
9215
9687
|
const title = createFieldLabel(element);
|
|
9216
9688
|
label.appendChild(title);
|
|
9217
9689
|
if (element.description || element.hint) {
|
|
@@ -9318,7 +9790,7 @@ function renderElement2(element, ctx) {
|
|
|
9318
9790
|
}
|
|
9319
9791
|
const initiallyDisabled2 = shouldDisableElement(element, ctx);
|
|
9320
9792
|
const outerWrapper = document.createElement("div");
|
|
9321
|
-
outerWrapper.className = "mb-
|
|
9793
|
+
outerWrapper.className = "mb-2 fb-field-wrapper fb-markdown-wrapper";
|
|
9322
9794
|
outerWrapper.setAttribute(
|
|
9323
9795
|
"data-field-key",
|
|
9324
9796
|
getElementLookupKey(element, ctx.state)
|
|
@@ -9334,7 +9806,7 @@ function renderElement2(element, ctx) {
|
|
|
9334
9806
|
}
|
|
9335
9807
|
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
9336
9808
|
const wrapper = document.createElement("div");
|
|
9337
|
-
wrapper.className = "mb-
|
|
9809
|
+
wrapper.className = "mb-2 fb-field-wrapper";
|
|
9338
9810
|
wrapper.setAttribute("data-field-key", element.key);
|
|
9339
9811
|
const label = createLabelContainer(element);
|
|
9340
9812
|
wrapper.appendChild(label);
|
|
@@ -9401,12 +9873,16 @@ var defaultConfig = {
|
|
|
9401
9873
|
hintPattern: "Format: {pattern}",
|
|
9402
9874
|
fileCountSingle: "{count} file",
|
|
9403
9875
|
fileCountPlural: "{count} files",
|
|
9876
|
+
fileCountWithMax: "{count} / {max} files",
|
|
9404
9877
|
fileCountRange: "({min}-{max})",
|
|
9405
9878
|
uploadingFile: "Uploading\u2026",
|
|
9406
9879
|
filesCounter: "{count}/{max}",
|
|
9407
9880
|
fromLibrary: "From library",
|
|
9408
9881
|
libraryEmpty: "Library is empty",
|
|
9409
9882
|
libraryHint: "Choose from previously uploaded files",
|
|
9883
|
+
dropToUpload: "Release to upload",
|
|
9884
|
+
replaceFile: "Replace",
|
|
9885
|
+
clearAll: "Clear all",
|
|
9410
9886
|
pickerError: "Failed to load files from library",
|
|
9411
9887
|
// Validation errors
|
|
9412
9888
|
required: "Required",
|
|
@@ -9425,6 +9901,7 @@ var defaultConfig = {
|
|
|
9425
9901
|
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
9426
9902
|
invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
|
|
9427
9903
|
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
9904
|
+
uploadFailed: 'Failed to upload "{name}": {error}',
|
|
9428
9905
|
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
9429
9906
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
9430
9907
|
invalidOption: "Invalid option",
|
|
@@ -9472,12 +9949,16 @@ var defaultConfig = {
|
|
|
9472
9949
|
hintPattern: "\u0424\u043E\u0440\u043C\u0430\u0442: {pattern}",
|
|
9473
9950
|
fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
|
|
9474
9951
|
fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9952
|
+
fileCountWithMax: "{count} / {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9475
9953
|
fileCountRange: "({min}-{max})",
|
|
9476
9954
|
uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
|
|
9477
9955
|
filesCounter: "{count}/{max}",
|
|
9478
9956
|
fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
9479
9957
|
libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
|
|
9480
9958
|
libraryHint: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437 \u0440\u0430\u043D\u0435\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9959
|
+
dropToUpload: "\u041E\u0442\u043F\u0443\u0441\u0442\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C",
|
|
9960
|
+
replaceFile: "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
9961
|
+
clearAll: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435",
|
|
9481
9962
|
pickerError: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B\u044B \u0438\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
9482
9963
|
// Validation errors
|
|
9483
9964
|
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
@@ -9496,6 +9977,7 @@ var defaultConfig = {
|
|
|
9496
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}',
|
|
9497
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})',
|
|
9498
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}',
|
|
9499
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",
|
|
9500
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}",
|
|
9501
9983
|
invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
|
|
@@ -9618,10 +10100,10 @@ var defaultTheme = {
|
|
|
9618
10100
|
fileUploadHoverBorderColor: "#3b82f6",
|
|
9619
10101
|
// blue-500
|
|
9620
10102
|
// Spacing
|
|
9621
|
-
inputPaddingX: "0.
|
|
9622
|
-
//
|
|
9623
|
-
inputPaddingY: "0.
|
|
9624
|
-
//
|
|
10103
|
+
inputPaddingX: "0.5rem",
|
|
10104
|
+
// 8px (compact density v2)
|
|
10105
|
+
inputPaddingY: "0.25rem",
|
|
10106
|
+
// 4px (compact density v2)
|
|
9625
10107
|
borderRadius: "0.5rem",
|
|
9626
10108
|
// rounded-lg (8px)
|
|
9627
10109
|
borderWidth: "1px",
|
|
@@ -9718,29 +10200,6 @@ var exampleThemes = {
|
|
|
9718
10200
|
}
|
|
9719
10201
|
};
|
|
9720
10202
|
|
|
9721
|
-
// src/utils/styles.ts
|
|
9722
|
-
function applyActionButtonStyles(button, isFormLevel = false) {
|
|
9723
|
-
button.style.cssText = `
|
|
9724
|
-
background-color: var(--fb-action-bg-color);
|
|
9725
|
-
color: var(--fb-action-text-color);
|
|
9726
|
-
border: var(--fb-border-width) solid var(--fb-action-border-color);
|
|
9727
|
-
padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
|
|
9728
|
-
font-size: var(--fb-font-size);
|
|
9729
|
-
font-weight: var(--fb-font-weight-medium);
|
|
9730
|
-
border-radius: var(--fb-border-radius);
|
|
9731
|
-
transition: all var(--fb-transition-duration);
|
|
9732
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
9733
|
-
`;
|
|
9734
|
-
button.addEventListener("mouseenter", () => {
|
|
9735
|
-
button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
|
|
9736
|
-
button.style.borderColor = "var(--fb-action-hover-border-color)";
|
|
9737
|
-
});
|
|
9738
|
-
button.addEventListener("mouseleave", () => {
|
|
9739
|
-
button.style.backgroundColor = "var(--fb-action-bg-color)";
|
|
9740
|
-
button.style.borderColor = "var(--fb-action-border-color)";
|
|
9741
|
-
});
|
|
9742
|
-
}
|
|
9743
|
-
|
|
9744
10203
|
// src/components/registry.ts
|
|
9745
10204
|
function validateHiddenElement(element, key, context) {
|
|
9746
10205
|
const { scopeRoot } = context;
|
|
@@ -10066,7 +10525,7 @@ var FormBuilderInstance = class {
|
|
|
10066
10525
|
existingContainer.remove();
|
|
10067
10526
|
}
|
|
10068
10527
|
const actionsContainer = document.createElement("div");
|
|
10069
|
-
actionsContainer.className = "form-level-actions-container mt-
|
|
10528
|
+
actionsContainer.className = "form-level-actions-container mt-3 pt-2 flex flex-wrap gap-2 justify-center";
|
|
10070
10529
|
actionsContainer.style.cssText = `
|
|
10071
10530
|
border-top: var(--fb-border-width) solid var(--fb-border-color);
|
|
10072
10531
|
`;
|
|
@@ -10207,7 +10666,7 @@ var FormBuilderInstance = class {
|
|
|
10207
10666
|
*/
|
|
10208
10667
|
createRootPrefillHints(hints) {
|
|
10209
10668
|
const hintsContainer = document.createElement("div");
|
|
10210
|
-
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-
|
|
10669
|
+
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
|
|
10211
10670
|
hints.forEach((hint) => {
|
|
10212
10671
|
const hintButton = document.createElement("button");
|
|
10213
10672
|
hintButton.type = "button";
|
|
@@ -10240,7 +10699,7 @@ var FormBuilderInstance = class {
|
|
|
10240
10699
|
root.setAttribute("data-fb-root", "true");
|
|
10241
10700
|
injectThemeVariables(root, this.state.config.theme);
|
|
10242
10701
|
const rootContainer = document.createElement("div");
|
|
10243
|
-
rootContainer.className = "space-y-
|
|
10702
|
+
rootContainer.className = "space-y-2";
|
|
10244
10703
|
if (schema.prefillHints && !this.state.config.readonly) {
|
|
10245
10704
|
const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
|
|
10246
10705
|
rootContainer.appendChild(hintsContainer);
|
|
@@ -10248,9 +10707,9 @@ var FormBuilderInstance = class {
|
|
|
10248
10707
|
const fieldsWrapper = document.createElement("div");
|
|
10249
10708
|
const columns = schema.columns || 1;
|
|
10250
10709
|
if (columns === 1) {
|
|
10251
|
-
fieldsWrapper.className = "space-y-
|
|
10710
|
+
fieldsWrapper.className = "space-y-2";
|
|
10252
10711
|
} else {
|
|
10253
|
-
fieldsWrapper.className = `grid grid-cols-${columns} gap-
|
|
10712
|
+
fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
10254
10713
|
}
|
|
10255
10714
|
schema.elements.forEach((element) => {
|
|
10256
10715
|
if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
|