@dmitryvim/form-builder 0.1.34 → 0.1.35
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/demo.js +12 -13
- package/dist/elements.html +161 -610
- package/dist/elements.js +231 -269
- package/dist/form-builder.js +258 -730
- package/dist/index.html +2 -27
- package/package.json +3 -8
package/dist/form-builder.js
CHANGED
|
@@ -1,56 +1,4 @@
|
|
|
1
1
|
// Form Builder Library - Core API
|
|
2
|
-
|
|
3
|
-
// Object URL management for memory leak prevention
|
|
4
|
-
const objectUrlIndex = new Map();
|
|
5
|
-
|
|
6
|
-
function createObjectURL(file, resourceIdParam) {
|
|
7
|
-
// Revoke previous URL for this resource to prevent memory leaks
|
|
8
|
-
revokeObjectURL(resourceIdParam);
|
|
9
|
-
|
|
10
|
-
const url = URL.createObjectURL(file);
|
|
11
|
-
objectUrlIndex.set(resourceIdParam, url);
|
|
12
|
-
return url;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function revokeObjectURL(resourceIdParam) {
|
|
16
|
-
const url = objectUrlIndex.get(resourceIdParam);
|
|
17
|
-
if (url) {
|
|
18
|
-
URL.revokeObjectURL(url);
|
|
19
|
-
objectUrlIndex.delete(resourceIdParam);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function revokeAllObjectURLs() {
|
|
24
|
-
for (const [, url] of objectUrlIndex.entries()) {
|
|
25
|
-
URL.revokeObjectURL(url);
|
|
26
|
-
}
|
|
27
|
-
objectUrlIndex.clear();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Helper function to create safe preview elements
|
|
31
|
-
function createPreviewElement(icon, fileName) {
|
|
32
|
-
const wrapper = document.createElement("div");
|
|
33
|
-
wrapper.className =
|
|
34
|
-
"aspect-video flex items-center justify-center text-gray-400";
|
|
35
|
-
|
|
36
|
-
const textCenter = document.createElement("div");
|
|
37
|
-
textCenter.className = "text-center";
|
|
38
|
-
|
|
39
|
-
const iconEl = document.createElement("div");
|
|
40
|
-
iconEl.className = "text-4xl mb-2";
|
|
41
|
-
iconEl.textContent = icon;
|
|
42
|
-
|
|
43
|
-
const nameEl = document.createElement("div");
|
|
44
|
-
nameEl.className = "text-sm";
|
|
45
|
-
nameEl.textContent = fileName || "";
|
|
46
|
-
|
|
47
|
-
textCenter.appendChild(iconEl);
|
|
48
|
-
textCenter.appendChild(nameEl);
|
|
49
|
-
wrapper.appendChild(textCenter);
|
|
50
|
-
|
|
51
|
-
return wrapper;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
2
|
// State management
|
|
55
3
|
const state = {
|
|
56
4
|
schema: null,
|
|
@@ -194,8 +142,6 @@ function renderForm(schema, prefill) {
|
|
|
194
142
|
return;
|
|
195
143
|
}
|
|
196
144
|
|
|
197
|
-
// Clean up any existing object URLs before clearing form
|
|
198
|
-
revokeAllObjectURLs();
|
|
199
145
|
clear(state.formRoot);
|
|
200
146
|
|
|
201
147
|
const formEl = document.createElement("div");
|
|
@@ -238,17 +184,8 @@ function renderElement(element, ctx) {
|
|
|
238
184
|
const infoBtn = document.createElement("button");
|
|
239
185
|
infoBtn.type = "button";
|
|
240
186
|
infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
svg.setAttribute("class", "w-4 h-4");
|
|
244
|
-
svg.setAttribute("fill", "currentColor");
|
|
245
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
246
|
-
|
|
247
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
248
|
-
path.setAttribute("d", "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z");
|
|
249
|
-
|
|
250
|
-
svg.appendChild(path);
|
|
251
|
-
infoBtn.appendChild(svg);
|
|
187
|
+
infoBtn.innerHTML =
|
|
188
|
+
'<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
|
|
252
189
|
|
|
253
190
|
// Create tooltip
|
|
254
191
|
const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -479,8 +416,8 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
479
416
|
reader.readAsDataURL(meta.file);
|
|
480
417
|
container.appendChild(img);
|
|
481
418
|
} else if (meta.type && meta.type.startsWith("video/")) {
|
|
482
|
-
// Video file - use
|
|
483
|
-
const videoUrl = createObjectURL(meta.file
|
|
419
|
+
// Video file - use object URL for preview
|
|
420
|
+
const videoUrl = URL.createObjectURL(meta.file);
|
|
484
421
|
|
|
485
422
|
// Remove all conflicting handlers to prevent interference with video controls
|
|
486
423
|
container.onclick = null;
|
|
@@ -490,55 +427,39 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
490
427
|
container.parentNode.replaceChild(newContainer, container);
|
|
491
428
|
container = newContainer;
|
|
492
429
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const fallback = document.createTextNode("Your browser does not support the video tag.");
|
|
510
|
-
video.appendChild(source);
|
|
511
|
-
video.appendChild(fallback);
|
|
512
|
-
|
|
513
|
-
const buttonsContainer = document.createElement("div");
|
|
514
|
-
buttonsContainer.className = "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1";
|
|
515
|
-
|
|
516
|
-
const deleteBtn = document.createElement("button");
|
|
517
|
-
deleteBtn.className = "bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn";
|
|
518
|
-
deleteBtn.textContent = t("removeElement");
|
|
519
|
-
|
|
520
|
-
const changeBtn = document.createElement("button");
|
|
521
|
-
changeBtn.className = "bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn";
|
|
522
|
-
changeBtn.textContent = "Change";
|
|
523
|
-
|
|
524
|
-
buttonsContainer.appendChild(deleteBtn);
|
|
525
|
-
buttonsContainer.appendChild(changeBtn);
|
|
526
|
-
wrapper.appendChild(video);
|
|
527
|
-
wrapper.appendChild(buttonsContainer);
|
|
528
|
-
container.appendChild(wrapper);
|
|
430
|
+
container.innerHTML = `
|
|
431
|
+
<div class="relative group h-full">
|
|
432
|
+
<video class="w-full h-full object-contain" controls preload="auto" muted>
|
|
433
|
+
<source src="${videoUrl}" type="${meta.type}">
|
|
434
|
+
Your browser does not support the video tag.
|
|
435
|
+
</video>
|
|
436
|
+
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
|
|
437
|
+
<button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
|
|
438
|
+
${t("removeElement")}
|
|
439
|
+
</button>
|
|
440
|
+
<button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
|
|
441
|
+
Change
|
|
442
|
+
</button>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
`;
|
|
529
446
|
|
|
530
447
|
// Add click handlers to the custom buttons
|
|
531
|
-
changeBtn
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
448
|
+
const changeBtn = container.querySelector(".change-file-btn");
|
|
449
|
+
if (changeBtn) {
|
|
450
|
+
changeBtn.onclick = (e) => {
|
|
451
|
+
e.stopPropagation();
|
|
452
|
+
if (deps?.picker) {
|
|
453
|
+
deps.picker.click();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
537
457
|
|
|
538
|
-
deleteBtn
|
|
458
|
+
const deleteBtn = container.querySelector(".delete-file-btn");
|
|
459
|
+
if (deleteBtn) {
|
|
460
|
+
deleteBtn.onclick = (e) => {
|
|
539
461
|
e.stopPropagation();
|
|
540
|
-
// Clear the file
|
|
541
|
-
revokeObjectURL(resourceId);
|
|
462
|
+
// Clear the file
|
|
542
463
|
state.resourceIndex.delete(resourceId);
|
|
543
464
|
// Update hidden input
|
|
544
465
|
const hiddenInput = container.parentElement.querySelector(
|
|
@@ -554,55 +475,27 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
554
475
|
if (deps?.dragHandler) {
|
|
555
476
|
setupDragAndDrop(container, deps.dragHandler);
|
|
556
477
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
svg.setAttribute("fill", "currentColor");
|
|
566
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
567
|
-
|
|
568
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
569
|
-
path.setAttribute("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");
|
|
570
|
-
|
|
571
|
-
svg.appendChild(path);
|
|
572
|
-
|
|
573
|
-
const textDiv = document.createElement("div");
|
|
574
|
-
textDiv.className = "text-sm text-center";
|
|
575
|
-
textDiv.textContent = t("clickDragText");
|
|
576
|
-
|
|
577
|
-
wrapper.appendChild(svg);
|
|
578
|
-
wrapper.appendChild(textDiv);
|
|
579
|
-
container.appendChild(wrapper);
|
|
478
|
+
container.innerHTML = `
|
|
479
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
480
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
481
|
+
<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"/>
|
|
482
|
+
</svg>
|
|
483
|
+
<div class="text-sm text-center">${t("clickDragText")}</div>
|
|
484
|
+
</div>
|
|
485
|
+
`;
|
|
580
486
|
};
|
|
487
|
+
}
|
|
581
488
|
} else {
|
|
582
|
-
// Non-image, non-video file
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const icon = document.createElement("div");
|
|
588
|
-
icon.className = "text-2xl mb-2";
|
|
589
|
-
icon.textContent = "📁";
|
|
590
|
-
|
|
591
|
-
const nameEl = document.createElement("div");
|
|
592
|
-
nameEl.className = "text-sm";
|
|
593
|
-
nameEl.textContent = fileName || "";
|
|
594
|
-
|
|
595
|
-
wrapper.appendChild(icon);
|
|
596
|
-
wrapper.appendChild(nameEl);
|
|
597
|
-
clear(container);
|
|
598
|
-
container.appendChild(wrapper);
|
|
489
|
+
// Non-image, non-video file
|
|
490
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">📁</div><div class="text-sm">${
|
|
491
|
+
fileName
|
|
492
|
+
}</div></div>`;
|
|
599
493
|
}
|
|
600
494
|
|
|
601
495
|
// Add delete button for edit mode (except for videos which have custom buttons)
|
|
602
496
|
if (!isReadonly && !(meta && meta.type && meta.type.startsWith("video/"))) {
|
|
603
497
|
addDeleteButton(container, () => {
|
|
604
|
-
// Clear the file
|
|
605
|
-
revokeObjectURL(resourceId);
|
|
498
|
+
// Clear the file
|
|
606
499
|
state.resourceIndex.delete(resourceId);
|
|
607
500
|
// Update hidden input
|
|
608
501
|
const hiddenInput = container.parentElement.querySelector(
|
|
@@ -611,29 +504,15 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
611
504
|
if (hiddenInput) {
|
|
612
505
|
hiddenInput.value = "";
|
|
613
506
|
}
|
|
614
|
-
// Clear preview and show placeholder
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
624
|
-
|
|
625
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
626
|
-
path.setAttribute("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");
|
|
627
|
-
|
|
628
|
-
svg.appendChild(path);
|
|
629
|
-
|
|
630
|
-
const textDiv = document.createElement("div");
|
|
631
|
-
textDiv.className = "text-sm text-center";
|
|
632
|
-
textDiv.textContent = t("clickDragText");
|
|
633
|
-
|
|
634
|
-
wrapper.appendChild(svg);
|
|
635
|
-
wrapper.appendChild(textDiv);
|
|
636
|
-
container.appendChild(wrapper);
|
|
507
|
+
// Clear preview and show placeholder
|
|
508
|
+
container.innerHTML = `
|
|
509
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
510
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
511
|
+
<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"/>
|
|
512
|
+
</svg>
|
|
513
|
+
<div class="text-sm text-center">${t("clickDragText")}</div>
|
|
514
|
+
</div>
|
|
515
|
+
`;
|
|
637
516
|
});
|
|
638
517
|
}
|
|
639
518
|
} else if (state.config.getThumbnail) {
|
|
@@ -646,50 +525,21 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
646
525
|
container.appendChild(img);
|
|
647
526
|
} else {
|
|
648
527
|
// Fallback to file icon
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
"flex flex-col items-center justify-center h-full text-gray-400";
|
|
653
|
-
|
|
654
|
-
const icon = document.createElement("div");
|
|
655
|
-
icon.className = "text-2xl mb-2";
|
|
656
|
-
icon.textContent = "🖼️";
|
|
657
|
-
|
|
658
|
-
const nameEl = document.createElement("div");
|
|
659
|
-
nameEl.className = "text-sm";
|
|
660
|
-
nameEl.textContent = fileName || "";
|
|
661
|
-
|
|
662
|
-
wrapper.appendChild(icon);
|
|
663
|
-
wrapper.appendChild(nameEl);
|
|
664
|
-
clear(container);
|
|
665
|
-
container.appendChild(wrapper);
|
|
528
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">🖼️</div><div class="text-sm">${
|
|
529
|
+
fileName
|
|
530
|
+
}</div></div>`;
|
|
666
531
|
}
|
|
667
532
|
} catch (error) {
|
|
668
533
|
console.warn("Thumbnail loading failed:", error);
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
"flex flex-col items-center justify-center h-full text-gray-400";
|
|
673
|
-
|
|
674
|
-
const icon = document.createElement("div");
|
|
675
|
-
icon.className = "text-2xl mb-2";
|
|
676
|
-
icon.textContent = "📁";
|
|
677
|
-
|
|
678
|
-
const nameEl = document.createElement("div");
|
|
679
|
-
nameEl.className = "text-sm";
|
|
680
|
-
nameEl.textContent = fileName || "";
|
|
681
|
-
|
|
682
|
-
wrapper.appendChild(icon);
|
|
683
|
-
wrapper.appendChild(nameEl);
|
|
684
|
-
clear(container);
|
|
685
|
-
container.appendChild(wrapper);
|
|
534
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">📁</div><div class="text-sm">${
|
|
535
|
+
fileName
|
|
536
|
+
}</div></div>`;
|
|
686
537
|
}
|
|
687
538
|
|
|
688
539
|
// Add delete button for edit mode
|
|
689
540
|
if (!isReadonly) {
|
|
690
541
|
addDeleteButton(container, () => {
|
|
691
|
-
// Clear the file
|
|
692
|
-
revokeObjectURL(resourceId);
|
|
542
|
+
// Clear the file
|
|
693
543
|
state.resourceIndex.delete(resourceId);
|
|
694
544
|
// Update hidden input
|
|
695
545
|
const hiddenInput = container.parentElement.querySelector(
|
|
@@ -698,35 +548,22 @@ async function renderFilePreview(container, resourceId, options = {}) {
|
|
|
698
548
|
if (hiddenInput) {
|
|
699
549
|
hiddenInput.value = "";
|
|
700
550
|
}
|
|
701
|
-
// Clear preview and show placeholder
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
711
|
-
|
|
712
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
713
|
-
path.setAttribute("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");
|
|
714
|
-
|
|
715
|
-
svg.appendChild(path);
|
|
716
|
-
|
|
717
|
-
const textDiv = document.createElement("div");
|
|
718
|
-
textDiv.className = "text-sm text-center";
|
|
719
|
-
textDiv.textContent = t("clickDragText");
|
|
720
|
-
|
|
721
|
-
wrapper.appendChild(svg);
|
|
722
|
-
wrapper.appendChild(textDiv);
|
|
723
|
-
container.appendChild(wrapper);
|
|
551
|
+
// Clear preview and show placeholder
|
|
552
|
+
container.innerHTML = `
|
|
553
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
554
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
555
|
+
<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"/>
|
|
556
|
+
</svg>
|
|
557
|
+
<div class="text-sm text-center">${t("clickDragText")}</div>
|
|
558
|
+
</div>
|
|
559
|
+
`;
|
|
724
560
|
});
|
|
725
561
|
}
|
|
726
562
|
} else {
|
|
727
|
-
// No file and no getThumbnail config - fallback
|
|
728
|
-
|
|
729
|
-
|
|
563
|
+
// No file and no getThumbnail config - fallback
|
|
564
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">🖼️</div><div class="text-sm">${
|
|
565
|
+
fileName
|
|
566
|
+
}</div></div>`;
|
|
730
567
|
}
|
|
731
568
|
|
|
732
569
|
// Add click handler for download in readonly mode
|
|
@@ -779,11 +616,11 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
779
616
|
slot.onclick = () => {
|
|
780
617
|
// Look for file input - check parent containers that have space-y-2 class
|
|
781
618
|
let filesWrapper = container.parentElement;
|
|
782
|
-
while (filesWrapper && !filesWrapper.classList.contains(
|
|
619
|
+
while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
|
|
783
620
|
filesWrapper = filesWrapper.parentElement;
|
|
784
621
|
}
|
|
785
622
|
// If no parent with space-y-2, container itself might be the wrapper
|
|
786
|
-
if (!filesWrapper && container.classList.contains(
|
|
623
|
+
if (!filesWrapper && container.classList.contains('space-y-2')) {
|
|
787
624
|
filesWrapper = container;
|
|
788
625
|
}
|
|
789
626
|
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
@@ -804,11 +641,11 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
804
641
|
e.stopPropagation();
|
|
805
642
|
// Look for file input - check parent containers that have space-y-2 class
|
|
806
643
|
let filesWrapper = container.parentElement;
|
|
807
|
-
while (filesWrapper && !filesWrapper.classList.contains(
|
|
644
|
+
while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
|
|
808
645
|
filesWrapper = filesWrapper.parentElement;
|
|
809
646
|
}
|
|
810
647
|
// If no parent with space-y-2, container itself might be the wrapper
|
|
811
|
-
if (!filesWrapper && container.classList.contains(
|
|
648
|
+
if (!filesWrapper && container.classList.contains('space-y-2')) {
|
|
812
649
|
filesWrapper = container;
|
|
813
650
|
}
|
|
814
651
|
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
@@ -873,179 +710,76 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
873
710
|
img.src = url;
|
|
874
711
|
slot.appendChild(img);
|
|
875
712
|
} else {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
svg.setAttribute("class", "w-12 h-12");
|
|
882
|
-
svg.setAttribute("fill", "currentColor");
|
|
883
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
884
|
-
|
|
885
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
886
|
-
path.setAttribute("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");
|
|
887
|
-
|
|
888
|
-
svg.appendChild(path);
|
|
889
|
-
wrapper.appendChild(svg);
|
|
890
|
-
slot.appendChild(wrapper);
|
|
713
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
714
|
+
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
715
|
+
<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"/>
|
|
716
|
+
</svg>
|
|
717
|
+
</div>`;
|
|
891
718
|
}
|
|
892
719
|
} else {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
svg.setAttribute("class", "w-12 h-12");
|
|
899
|
-
svg.setAttribute("fill", "currentColor");
|
|
900
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
901
|
-
|
|
902
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
903
|
-
path.setAttribute("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");
|
|
904
|
-
|
|
905
|
-
svg.appendChild(path);
|
|
906
|
-
wrapper.appendChild(svg);
|
|
907
|
-
slot.appendChild(wrapper);
|
|
720
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
721
|
+
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
722
|
+
<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"/>
|
|
723
|
+
</svg>
|
|
724
|
+
</div>`;
|
|
908
725
|
}
|
|
909
726
|
} else if (meta && meta.type?.startsWith("video/")) {
|
|
910
727
|
if (meta.file && meta.file instanceof File) {
|
|
911
|
-
// Video file - use
|
|
912
|
-
const videoUrl = createObjectURL(meta.file
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const overlay = document.createElement("div");
|
|
929
|
-
overlay.className = "absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center";
|
|
930
|
-
|
|
931
|
-
const playButton = document.createElement("div");
|
|
932
|
-
playButton.className = "bg-white bg-opacity-90 rounded-full p-1";
|
|
933
|
-
|
|
934
|
-
const playIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
935
|
-
playIcon.setAttribute("class", "w-4 h-4 text-gray-800");
|
|
936
|
-
playIcon.setAttribute("fill", "currentColor");
|
|
937
|
-
playIcon.setAttribute("viewBox", "0 0 24 24");
|
|
938
|
-
|
|
939
|
-
const playPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
940
|
-
playPath.setAttribute("d", "M8 5v14l11-7z");
|
|
941
|
-
|
|
942
|
-
playIcon.appendChild(playPath);
|
|
943
|
-
playButton.appendChild(playIcon);
|
|
944
|
-
overlay.appendChild(playButton);
|
|
945
|
-
wrapper.appendChild(video);
|
|
946
|
-
wrapper.appendChild(overlay);
|
|
947
|
-
slot.appendChild(wrapper);
|
|
728
|
+
// Video file - use object URL for preview in thumbnail format
|
|
729
|
+
const videoUrl = URL.createObjectURL(meta.file);
|
|
730
|
+
slot.innerHTML = `
|
|
731
|
+
<div class="relative group h-full w-full">
|
|
732
|
+
<video class="w-full h-full object-contain" preload="metadata" muted>
|
|
733
|
+
<source src="${videoUrl}" type="${meta.type}">
|
|
734
|
+
</video>
|
|
735
|
+
<div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
|
|
736
|
+
<div class="bg-white bg-opacity-90 rounded-full p-1">
|
|
737
|
+
<svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
738
|
+
<path d="M8 5v14l11-7z"/>
|
|
739
|
+
</svg>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
`;
|
|
948
744
|
} else if (state.config.getThumbnail) {
|
|
949
745
|
// Use getThumbnail for uploaded video files
|
|
950
746
|
const videoUrl = state.config.getThumbnail(rid);
|
|
951
747
|
if (videoUrl) {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const overlay = document.createElement("div");
|
|
968
|
-
overlay.className = "absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center";
|
|
969
|
-
|
|
970
|
-
const playButton = document.createElement("div");
|
|
971
|
-
playButton.className = "bg-white bg-opacity-90 rounded-full p-1";
|
|
972
|
-
|
|
973
|
-
const playIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
974
|
-
playIcon.setAttribute("class", "w-4 h-4 text-gray-800");
|
|
975
|
-
playIcon.setAttribute("fill", "currentColor");
|
|
976
|
-
playIcon.setAttribute("viewBox", "0 0 24 24");
|
|
977
|
-
|
|
978
|
-
const playPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
979
|
-
playPath.setAttribute("d", "M8 5v14l11-7z");
|
|
980
|
-
|
|
981
|
-
playIcon.appendChild(playPath);
|
|
982
|
-
playButton.appendChild(playIcon);
|
|
983
|
-
overlay.appendChild(playButton);
|
|
984
|
-
wrapper.appendChild(video);
|
|
985
|
-
wrapper.appendChild(overlay);
|
|
986
|
-
slot.appendChild(wrapper);
|
|
748
|
+
slot.innerHTML = `
|
|
749
|
+
<div class="relative group h-full w-full">
|
|
750
|
+
<video class="w-full h-full object-contain" preload="metadata" muted>
|
|
751
|
+
<source src="${videoUrl}" type="${meta.type}">
|
|
752
|
+
</video>
|
|
753
|
+
<div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
|
|
754
|
+
<div class="bg-white bg-opacity-90 rounded-full p-1">
|
|
755
|
+
<svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
756
|
+
<path d="M8 5v14l11-7z"/>
|
|
757
|
+
</svg>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
`;
|
|
987
762
|
} else {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
svg.setAttribute("fill", "currentColor");
|
|
995
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
996
|
-
|
|
997
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
998
|
-
path.setAttribute("d", "M8 5v14l11-7z");
|
|
999
|
-
|
|
1000
|
-
svg.appendChild(path);
|
|
1001
|
-
|
|
1002
|
-
const nameDiv = document.createElement("div");
|
|
1003
|
-
nameDiv.className = "text-xs mt-1";
|
|
1004
|
-
nameDiv.textContent = meta?.name || "Video";
|
|
1005
|
-
|
|
1006
|
-
wrapper.appendChild(svg);
|
|
1007
|
-
wrapper.appendChild(nameDiv);
|
|
1008
|
-
slot.appendChild(wrapper);
|
|
763
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
764
|
+
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
765
|
+
<path d="M8 5v14l11-7z"/>
|
|
766
|
+
</svg>
|
|
767
|
+
<div class="text-xs mt-1">${meta?.name || "Video"}</div>
|
|
768
|
+
</div>`;
|
|
1009
769
|
}
|
|
1010
770
|
} else {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
svg.setAttribute("fill", "currentColor");
|
|
1018
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
1019
|
-
|
|
1020
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1021
|
-
path.setAttribute("d", "M8 5v14l11-7z");
|
|
1022
|
-
|
|
1023
|
-
svg.appendChild(path);
|
|
1024
|
-
|
|
1025
|
-
const nameDiv = document.createElement("div");
|
|
1026
|
-
nameDiv.className = "text-xs mt-1";
|
|
1027
|
-
nameDiv.textContent = meta?.name || "Video";
|
|
1028
|
-
|
|
1029
|
-
wrapper.appendChild(svg);
|
|
1030
|
-
wrapper.appendChild(nameDiv);
|
|
1031
|
-
slot.appendChild(wrapper);
|
|
771
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
772
|
+
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
773
|
+
<path d="M8 5v14l11-7z"/>
|
|
774
|
+
</svg>
|
|
775
|
+
<div class="text-xs mt-1">${meta?.name || "Video"}</div>
|
|
776
|
+
</div>`;
|
|
1032
777
|
}
|
|
1033
778
|
} else {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const iconDiv = document.createElement("div");
|
|
1039
|
-
iconDiv.className = "text-2xl mb-1";
|
|
1040
|
-
iconDiv.textContent = "📁";
|
|
1041
|
-
|
|
1042
|
-
const nameDiv = document.createElement("div");
|
|
1043
|
-
nameDiv.className = "text-xs";
|
|
1044
|
-
nameDiv.textContent = meta?.name || "File";
|
|
1045
|
-
|
|
1046
|
-
wrapper.appendChild(iconDiv);
|
|
1047
|
-
wrapper.appendChild(nameDiv);
|
|
1048
|
-
slot.appendChild(wrapper);
|
|
779
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
780
|
+
<div class="text-2xl mb-1">📁</div>
|
|
781
|
+
<div class="text-xs">${meta?.name || "File"}</div>
|
|
782
|
+
</div>`;
|
|
1049
783
|
}
|
|
1050
784
|
|
|
1051
785
|
// Add remove button overlay (similar to file field)
|
|
@@ -1069,25 +803,16 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
1069
803
|
// Empty slot placeholder
|
|
1070
804
|
slot.className =
|
|
1071
805
|
"aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
svg.setAttribute("class", "w-12 h-12 text-gray-400");
|
|
1075
|
-
svg.setAttribute("fill", "currentColor");
|
|
1076
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
1077
|
-
|
|
1078
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1079
|
-
path.setAttribute("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");
|
|
1080
|
-
|
|
1081
|
-
svg.appendChild(path);
|
|
1082
|
-
slot.appendChild(svg);
|
|
806
|
+
slot.innerHTML =
|
|
807
|
+
'<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>';
|
|
1083
808
|
slot.onclick = () => {
|
|
1084
809
|
// Look for file input - check parent containers that have space-y-2 class
|
|
1085
810
|
let filesWrapper = container.parentElement;
|
|
1086
|
-
while (filesWrapper && !filesWrapper.classList.contains(
|
|
811
|
+
while (filesWrapper && !filesWrapper.classList.contains('space-y-2')) {
|
|
1087
812
|
filesWrapper = filesWrapper.parentElement;
|
|
1088
813
|
}
|
|
1089
814
|
// If no parent with space-y-2, container itself might be the wrapper
|
|
1090
|
-
if (!filesWrapper && container.classList.contains(
|
|
815
|
+
if (!filesWrapper && container.classList.contains('space-y-2')) {
|
|
1091
816
|
filesWrapper = container;
|
|
1092
817
|
}
|
|
1093
818
|
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
@@ -1141,37 +866,14 @@ async function handleFileSelect(file, container, fieldName, deps = null) {
|
|
|
1141
866
|
});
|
|
1142
867
|
|
|
1143
868
|
// Create hidden input to store the resource ID
|
|
1144
|
-
|
|
1145
|
-
let parentElement = container.parentElement;
|
|
1146
|
-
if (!parentElement) {
|
|
1147
|
-
// Container is detached - find the current container by field name
|
|
1148
|
-
const form = document.querySelector('[data-form-builder]');
|
|
1149
|
-
if (form) {
|
|
1150
|
-
const existingInput = form.querySelector(`input[name="${fieldName}"]`);
|
|
1151
|
-
if (existingInput) {
|
|
1152
|
-
parentElement = existingInput.parentElement;
|
|
1153
|
-
// Update container reference to the current one in DOM
|
|
1154
|
-
const currentContainer = parentElement.querySelector('.file-preview-container');
|
|
1155
|
-
if (currentContainer) {
|
|
1156
|
-
container = currentContainer;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (!parentElement) {
|
|
1163
|
-
console.warn('Could not find parent element for file field:', fieldName);
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
let hiddenInput = parentElement.querySelector(
|
|
869
|
+
let hiddenInput = container.parentElement.querySelector(
|
|
1168
870
|
'input[type="hidden"]',
|
|
1169
871
|
);
|
|
1170
872
|
if (!hiddenInput) {
|
|
1171
873
|
hiddenInput = document.createElement("input");
|
|
1172
874
|
hiddenInput.type = "hidden";
|
|
1173
875
|
hiddenInput.name = fieldName;
|
|
1174
|
-
parentElement.appendChild(hiddenInput);
|
|
876
|
+
container.parentElement.appendChild(hiddenInput);
|
|
1175
877
|
}
|
|
1176
878
|
hiddenInput.value = rid;
|
|
1177
879
|
|
|
@@ -1342,17 +1044,11 @@ function validateForm(skipValidation = false) {
|
|
|
1342
1044
|
values.push(val);
|
|
1343
1045
|
|
|
1344
1046
|
if (!skipValidation && val) {
|
|
1345
|
-
if (
|
|
1346
|
-
element.minLength !== null &&
|
|
1347
|
-
val.length < element.minLength
|
|
1348
|
-
) {
|
|
1047
|
+
if (element.minLength !== null && val.length < element.minLength) {
|
|
1349
1048
|
errors.push(`${key}[${index}]: minLength=${element.minLength}`);
|
|
1350
1049
|
markValidity(input, `minLength=${element.minLength}`);
|
|
1351
1050
|
}
|
|
1352
|
-
if (
|
|
1353
|
-
element.maxLength !== null &&
|
|
1354
|
-
val.length > element.maxLength
|
|
1355
|
-
) {
|
|
1051
|
+
if (element.maxLength !== null && val.length > element.maxLength) {
|
|
1356
1052
|
errors.push(`${key}[${index}]: maxLength=${element.maxLength}`);
|
|
1357
1053
|
markValidity(input, `maxLength=${element.maxLength}`);
|
|
1358
1054
|
}
|
|
@@ -1377,7 +1073,7 @@ function validateForm(skipValidation = false) {
|
|
|
1377
1073
|
if (!skipValidation) {
|
|
1378
1074
|
const minCount = element.minCount ?? 1;
|
|
1379
1075
|
const maxCount = element.maxCount ?? 10;
|
|
1380
|
-
const nonEmptyValues = values.filter(
|
|
1076
|
+
const nonEmptyValues = values.filter(v => v.trim() !== "");
|
|
1381
1077
|
|
|
1382
1078
|
if (element.required && nonEmptyValues.length === 0) {
|
|
1383
1079
|
errors.push(`${key}: required`);
|
|
@@ -1460,9 +1156,7 @@ function validateForm(skipValidation = false) {
|
|
|
1460
1156
|
markValidity(input, `> max=${element.max}`);
|
|
1461
1157
|
}
|
|
1462
1158
|
|
|
1463
|
-
const d = Number.isInteger(element.decimals ?? 0)
|
|
1464
|
-
? element.decimals
|
|
1465
|
-
: 0;
|
|
1159
|
+
const d = Number.isInteger(element.decimals ?? 0) ? element.decimals : 0;
|
|
1466
1160
|
markValidity(input, null);
|
|
1467
1161
|
values.push(Number(v.toFixed(d)));
|
|
1468
1162
|
});
|
|
@@ -1471,7 +1165,7 @@ function validateForm(skipValidation = false) {
|
|
|
1471
1165
|
if (!skipValidation) {
|
|
1472
1166
|
const minCount = element.minCount ?? 1;
|
|
1473
1167
|
const maxCount = element.maxCount ?? 10;
|
|
1474
|
-
const nonNullValues = values.filter(
|
|
1168
|
+
const nonNullValues = values.filter(v => v !== null);
|
|
1475
1169
|
|
|
1476
1170
|
if (element.required && nonNullValues.length === 0) {
|
|
1477
1171
|
errors.push(`${key}: required`);
|
|
@@ -1535,7 +1229,7 @@ function validateForm(skipValidation = false) {
|
|
|
1535
1229
|
if (!skipValidation) {
|
|
1536
1230
|
const minCount = element.minCount ?? 1;
|
|
1537
1231
|
const maxCount = element.maxCount ?? 10;
|
|
1538
|
-
const nonEmptyValues = values.filter(
|
|
1232
|
+
const nonEmptyValues = values.filter(v => v !== "");
|
|
1539
1233
|
|
|
1540
1234
|
if (element.required && nonEmptyValues.length === 0) {
|
|
1541
1235
|
errors.push(`${key}: required`);
|
|
@@ -1567,11 +1261,9 @@ function validateForm(skipValidation = false) {
|
|
|
1567
1261
|
// Handle file with multiple property like files type
|
|
1568
1262
|
// Find the files list by locating the specific file input for this field
|
|
1569
1263
|
const fullKey = pathJoin(ctx.path, key);
|
|
1570
|
-
const pickerInput = scopeRoot.querySelector(
|
|
1571
|
-
|
|
1572
|
-
);
|
|
1573
|
-
const filesWrapper = pickerInput?.closest(".space-y-2");
|
|
1574
|
-
const container = filesWrapper?.querySelector(".files-list") || null;
|
|
1264
|
+
const pickerInput = scopeRoot.querySelector(`input[type="file"][name="${fullKey}"]`);
|
|
1265
|
+
const filesWrapper = pickerInput?.closest('.space-y-2');
|
|
1266
|
+
const container = filesWrapper?.querySelector('.files-list') || null;
|
|
1575
1267
|
|
|
1576
1268
|
const resourceIds = [];
|
|
1577
1269
|
if (container) {
|
|
@@ -1659,18 +1351,12 @@ function validateForm(skipValidation = false) {
|
|
|
1659
1351
|
const items = [];
|
|
1660
1352
|
// Use full path for nested group element search
|
|
1661
1353
|
const fullKey = pathJoin(ctx.path, key);
|
|
1662
|
-
const itemElements = scopeRoot.querySelectorAll(
|
|
1663
|
-
`[name^="${fullKey}["]`,
|
|
1664
|
-
);
|
|
1354
|
+
const itemElements = scopeRoot.querySelectorAll(`[name^="${fullKey}["]`);
|
|
1665
1355
|
|
|
1666
1356
|
// Extract actual indices from DOM element names instead of assuming sequential numbering
|
|
1667
1357
|
const actualIndices = new Set();
|
|
1668
1358
|
itemElements.forEach((el) => {
|
|
1669
|
-
const match = el.name.match(
|
|
1670
|
-
new RegExp(
|
|
1671
|
-
`^${fullKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\[(\\d+)\\]`,
|
|
1672
|
-
),
|
|
1673
|
-
);
|
|
1359
|
+
const match = el.name.match(new RegExp(`^${fullKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\[(\\d+)\\]`));
|
|
1674
1360
|
if (match) {
|
|
1675
1361
|
actualIndices.add(parseInt(match[1]));
|
|
1676
1362
|
}
|
|
@@ -1688,8 +1374,7 @@ function validateForm(skipValidation = false) {
|
|
|
1688
1374
|
element.elements.forEach((child) => {
|
|
1689
1375
|
if (child.hidden) {
|
|
1690
1376
|
// For hidden child elements, use their default value
|
|
1691
|
-
itemData[child.key] =
|
|
1692
|
-
child.default !== undefined ? child.default : "";
|
|
1377
|
+
itemData[child.key] = child.default !== undefined ? child.default : "";
|
|
1693
1378
|
} else {
|
|
1694
1379
|
const childKey = `${fullKey}[${actualIndex}].${child.key}`;
|
|
1695
1380
|
itemData[child.key] = validateElement(
|
|
@@ -1710,8 +1395,7 @@ function validateForm(skipValidation = false) {
|
|
|
1710
1395
|
element.elements.forEach((child) => {
|
|
1711
1396
|
if (child.hidden) {
|
|
1712
1397
|
// For hidden child elements, use their default value
|
|
1713
|
-
groupData[child.key] =
|
|
1714
|
-
child.default !== undefined ? child.default : "";
|
|
1398
|
+
groupData[child.key] = child.default !== undefined ? child.default : "";
|
|
1715
1399
|
} else {
|
|
1716
1400
|
const childKey = `${key}.${child.key}`;
|
|
1717
1401
|
groupData[child.key] = validateElement(
|
|
@@ -1743,8 +1427,7 @@ function validateForm(skipValidation = false) {
|
|
|
1743
1427
|
element.elements.forEach((child) => {
|
|
1744
1428
|
if (child.hidden) {
|
|
1745
1429
|
// For hidden child elements, use their default value
|
|
1746
|
-
itemData[child.key] =
|
|
1747
|
-
child.default !== undefined ? child.default : "";
|
|
1430
|
+
itemData[child.key] = child.default !== undefined ? child.default : "";
|
|
1748
1431
|
} else {
|
|
1749
1432
|
const childKey = `${key}[${i}].${child.key}`;
|
|
1750
1433
|
itemData[child.key] = validateElement(
|
|
@@ -1782,8 +1465,7 @@ function validateForm(skipValidation = false) {
|
|
|
1782
1465
|
element.elements.forEach((child) => {
|
|
1783
1466
|
if (child.hidden) {
|
|
1784
1467
|
// For hidden child elements, use their default value
|
|
1785
|
-
containerData[child.key] =
|
|
1786
|
-
child.default !== undefined ? child.default : "";
|
|
1468
|
+
containerData[child.key] = child.default !== undefined ? child.default : "";
|
|
1787
1469
|
} else {
|
|
1788
1470
|
const childKey = `${key}.${child.key}`;
|
|
1789
1471
|
containerData[child.key] = validateElement(
|
|
@@ -1868,8 +1550,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1868
1550
|
|
|
1869
1551
|
const textInput = document.createElement("input");
|
|
1870
1552
|
textInput.type = "text";
|
|
1871
|
-
textInput.className =
|
|
1872
|
-
"flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1553
|
+
textInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1873
1554
|
textInput.placeholder = element.placeholder || "Enter text";
|
|
1874
1555
|
textInput.value = value;
|
|
1875
1556
|
textInput.readOnly = state.config.readonly;
|
|
@@ -1890,16 +1571,15 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1890
1571
|
|
|
1891
1572
|
function updateRemoveButtons() {
|
|
1892
1573
|
if (state.config.readonly) return;
|
|
1893
|
-
const items = container.querySelectorAll(
|
|
1574
|
+
const items = container.querySelectorAll('.multiple-text-item');
|
|
1894
1575
|
const currentCount = items.length;
|
|
1895
1576
|
items.forEach((item) => {
|
|
1896
|
-
let removeBtn = item.querySelector(
|
|
1577
|
+
let removeBtn = item.querySelector('.remove-item-btn');
|
|
1897
1578
|
if (!removeBtn) {
|
|
1898
|
-
removeBtn = document.createElement(
|
|
1899
|
-
removeBtn.type =
|
|
1900
|
-
removeBtn.className =
|
|
1901
|
-
|
|
1902
|
-
removeBtn.textContent = "✕";
|
|
1579
|
+
removeBtn = document.createElement('button');
|
|
1580
|
+
removeBtn.type = 'button';
|
|
1581
|
+
removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
|
|
1582
|
+
removeBtn.innerHTML = '✕';
|
|
1903
1583
|
removeBtn.onclick = () => {
|
|
1904
1584
|
const currentIndex = Array.from(container.children).indexOf(item);
|
|
1905
1585
|
if (container.children.length > minCount) {
|
|
@@ -1914,8 +1594,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1914
1594
|
}
|
|
1915
1595
|
const disabled = currentCount <= minCount;
|
|
1916
1596
|
removeBtn.disabled = disabled;
|
|
1917
|
-
removeBtn.style.opacity = disabled ?
|
|
1918
|
-
removeBtn.style.pointerEvents = disabled ?
|
|
1597
|
+
removeBtn.style.opacity = disabled ? '0.5' : '1';
|
|
1598
|
+
removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
|
|
1919
1599
|
});
|
|
1920
1600
|
}
|
|
1921
1601
|
|
|
@@ -1926,9 +1606,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1926
1606
|
if (!state.config.readonly && values.length < maxCount) {
|
|
1927
1607
|
const addBtn = document.createElement("button");
|
|
1928
1608
|
addBtn.type = "button";
|
|
1929
|
-
addBtn.className =
|
|
1930
|
-
|
|
1931
|
-
addBtn.textContent = `+ Add ${element.label || "Text"}`;
|
|
1609
|
+
addBtn.className = "add-text-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
1610
|
+
addBtn.textContent = `+ Add ${element.label || 'Text'}`;
|
|
1932
1611
|
addBtn.onclick = () => {
|
|
1933
1612
|
values.push(element.default || "");
|
|
1934
1613
|
addTextItem(element.default || "");
|
|
@@ -1940,7 +1619,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1940
1619
|
}
|
|
1941
1620
|
|
|
1942
1621
|
// Render initial items
|
|
1943
|
-
values.forEach(
|
|
1622
|
+
values.forEach(value => addTextItem(value));
|
|
1944
1623
|
updateAddButton();
|
|
1945
1624
|
updateRemoveButtons();
|
|
1946
1625
|
|
|
@@ -2000,8 +1679,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
2000
1679
|
itemWrapper.className = "multiple-textarea-item";
|
|
2001
1680
|
|
|
2002
1681
|
const textareaInput = document.createElement("textarea");
|
|
2003
|
-
textareaInput.className =
|
|
2004
|
-
"w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
1682
|
+
textareaInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
2005
1683
|
textareaInput.placeholder = element.placeholder || "Enter text";
|
|
2006
1684
|
textareaInput.rows = element.rows || 4;
|
|
2007
1685
|
textareaInput.value = value;
|
|
@@ -2023,16 +1701,15 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
2023
1701
|
|
|
2024
1702
|
function updateRemoveButtons() {
|
|
2025
1703
|
if (state.config.readonly) return;
|
|
2026
|
-
const items = container.querySelectorAll(
|
|
1704
|
+
const items = container.querySelectorAll('.multiple-textarea-item');
|
|
2027
1705
|
const currentCount = items.length;
|
|
2028
1706
|
items.forEach((item) => {
|
|
2029
|
-
let removeBtn = item.querySelector(
|
|
1707
|
+
let removeBtn = item.querySelector('.remove-item-btn');
|
|
2030
1708
|
if (!removeBtn) {
|
|
2031
|
-
removeBtn = document.createElement(
|
|
2032
|
-
removeBtn.type =
|
|
2033
|
-
removeBtn.className =
|
|
2034
|
-
|
|
2035
|
-
removeBtn.textContent = "✕ Remove";
|
|
1709
|
+
removeBtn = document.createElement('button');
|
|
1710
|
+
removeBtn.type = 'button';
|
|
1711
|
+
removeBtn.className = 'remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm';
|
|
1712
|
+
removeBtn.innerHTML = '✕ Remove';
|
|
2036
1713
|
removeBtn.onclick = () => {
|
|
2037
1714
|
const currentIndex = Array.from(container.children).indexOf(item);
|
|
2038
1715
|
if (container.children.length > minCount) {
|
|
@@ -2047,8 +1724,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
2047
1724
|
}
|
|
2048
1725
|
const disabled = currentCount <= minCount;
|
|
2049
1726
|
removeBtn.disabled = disabled;
|
|
2050
|
-
removeBtn.style.opacity = disabled ?
|
|
2051
|
-
removeBtn.style.pointerEvents = disabled ?
|
|
1727
|
+
removeBtn.style.opacity = disabled ? '0.5' : '1';
|
|
1728
|
+
removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
|
|
2052
1729
|
});
|
|
2053
1730
|
}
|
|
2054
1731
|
|
|
@@ -2059,9 +1736,8 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
2059
1736
|
if (!state.config.readonly && values.length < maxCount) {
|
|
2060
1737
|
const addBtn = document.createElement("button");
|
|
2061
1738
|
addBtn.type = "button";
|
|
2062
|
-
addBtn.className =
|
|
2063
|
-
|
|
2064
|
-
addBtn.textContent = `+ Add ${element.label || "Textarea"}`;
|
|
1739
|
+
addBtn.className = "add-textarea-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
1740
|
+
addBtn.textContent = `+ Add ${element.label || 'Textarea'}`;
|
|
2065
1741
|
addBtn.onclick = () => {
|
|
2066
1742
|
values.push(element.default || "");
|
|
2067
1743
|
addTextareaItem(element.default || "");
|
|
@@ -2073,7 +1749,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
2073
1749
|
}
|
|
2074
1750
|
|
|
2075
1751
|
// Render initial items
|
|
2076
|
-
values.forEach(
|
|
1752
|
+
values.forEach(value => addTextareaItem(value));
|
|
2077
1753
|
updateAddButton();
|
|
2078
1754
|
updateRemoveButtons();
|
|
2079
1755
|
|
|
@@ -2137,8 +1813,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
2137
1813
|
|
|
2138
1814
|
const numberInput = document.createElement("input");
|
|
2139
1815
|
numberInput.type = "number";
|
|
2140
|
-
numberInput.className =
|
|
2141
|
-
"flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1816
|
+
numberInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
2142
1817
|
numberInput.placeholder = element.placeholder || "0";
|
|
2143
1818
|
if (element.min !== undefined) numberInput.min = element.min;
|
|
2144
1819
|
if (element.max !== undefined) numberInput.max = element.max;
|
|
@@ -2162,16 +1837,15 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
2162
1837
|
|
|
2163
1838
|
function updateRemoveButtons() {
|
|
2164
1839
|
if (state.config.readonly) return;
|
|
2165
|
-
const items = container.querySelectorAll(
|
|
1840
|
+
const items = container.querySelectorAll('.multiple-number-item');
|
|
2166
1841
|
const currentCount = items.length;
|
|
2167
1842
|
items.forEach((item) => {
|
|
2168
|
-
let removeBtn = item.querySelector(
|
|
1843
|
+
let removeBtn = item.querySelector('.remove-item-btn');
|
|
2169
1844
|
if (!removeBtn) {
|
|
2170
|
-
removeBtn = document.createElement(
|
|
2171
|
-
removeBtn.type =
|
|
2172
|
-
removeBtn.className =
|
|
2173
|
-
|
|
2174
|
-
removeBtn.textContent = "✕";
|
|
1845
|
+
removeBtn = document.createElement('button');
|
|
1846
|
+
removeBtn.type = 'button';
|
|
1847
|
+
removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
|
|
1848
|
+
removeBtn.innerHTML = '✕';
|
|
2175
1849
|
removeBtn.onclick = () => {
|
|
2176
1850
|
const currentIndex = Array.from(container.children).indexOf(item);
|
|
2177
1851
|
if (container.children.length > minCount) {
|
|
@@ -2186,8 +1860,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
2186
1860
|
}
|
|
2187
1861
|
const disabled = currentCount <= minCount;
|
|
2188
1862
|
removeBtn.disabled = disabled;
|
|
2189
|
-
removeBtn.style.opacity = disabled ?
|
|
2190
|
-
removeBtn.style.pointerEvents = disabled ?
|
|
1863
|
+
removeBtn.style.opacity = disabled ? '0.5' : '1';
|
|
1864
|
+
removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
|
|
2191
1865
|
});
|
|
2192
1866
|
}
|
|
2193
1867
|
|
|
@@ -2198,9 +1872,8 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
2198
1872
|
if (!state.config.readonly && values.length < maxCount) {
|
|
2199
1873
|
const addBtn = document.createElement("button");
|
|
2200
1874
|
addBtn.type = "button";
|
|
2201
|
-
addBtn.className =
|
|
2202
|
-
|
|
2203
|
-
addBtn.textContent = `+ Add ${element.label || "Number"}`;
|
|
1875
|
+
addBtn.className = "add-number-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
1876
|
+
addBtn.textContent = `+ Add ${element.label || 'Number'}`;
|
|
2204
1877
|
addBtn.onclick = () => {
|
|
2205
1878
|
values.push(element.default || "");
|
|
2206
1879
|
addNumberItem(element.default || "");
|
|
@@ -2212,7 +1885,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
2212
1885
|
}
|
|
2213
1886
|
|
|
2214
1887
|
// Render initial items
|
|
2215
|
-
values.forEach(
|
|
1888
|
+
values.forEach(value => addNumberItem(value));
|
|
2216
1889
|
updateAddButton();
|
|
2217
1890
|
updateRemoveButtons();
|
|
2218
1891
|
|
|
@@ -2258,7 +1931,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2258
1931
|
const maxCount = element.maxCount ?? 10;
|
|
2259
1932
|
|
|
2260
1933
|
while (values.length < minCount) {
|
|
2261
|
-
values.push(element.default || element.options?.[0]?.value || "");
|
|
1934
|
+
values.push(element.default || (element.options?.[0]?.value || ""));
|
|
2262
1935
|
}
|
|
2263
1936
|
|
|
2264
1937
|
const container = document.createElement("div");
|
|
@@ -2280,8 +1953,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2280
1953
|
itemWrapper.className = "multiple-select-item flex items-center gap-2";
|
|
2281
1954
|
|
|
2282
1955
|
const selectInput = document.createElement("select");
|
|
2283
|
-
selectInput.className =
|
|
2284
|
-
"flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1956
|
+
selectInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
2285
1957
|
selectInput.disabled = state.config.readonly;
|
|
2286
1958
|
|
|
2287
1959
|
// Add options
|
|
@@ -2311,16 +1983,15 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2311
1983
|
|
|
2312
1984
|
function updateRemoveButtons() {
|
|
2313
1985
|
if (state.config.readonly) return;
|
|
2314
|
-
const items = container.querySelectorAll(
|
|
1986
|
+
const items = container.querySelectorAll('.multiple-select-item');
|
|
2315
1987
|
const currentCount = items.length;
|
|
2316
1988
|
items.forEach((item) => {
|
|
2317
|
-
let removeBtn = item.querySelector(
|
|
1989
|
+
let removeBtn = item.querySelector('.remove-item-btn');
|
|
2318
1990
|
if (!removeBtn) {
|
|
2319
|
-
removeBtn = document.createElement(
|
|
2320
|
-
removeBtn.type =
|
|
2321
|
-
removeBtn.className =
|
|
2322
|
-
|
|
2323
|
-
removeBtn.textContent = "✕";
|
|
1991
|
+
removeBtn = document.createElement('button');
|
|
1992
|
+
removeBtn.type = 'button';
|
|
1993
|
+
removeBtn.className = 'remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded';
|
|
1994
|
+
removeBtn.innerHTML = '✕';
|
|
2324
1995
|
removeBtn.onclick = () => {
|
|
2325
1996
|
const currentIndex = Array.from(container.children).indexOf(item);
|
|
2326
1997
|
if (container.children.length > minCount) {
|
|
@@ -2335,8 +2006,8 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2335
2006
|
}
|
|
2336
2007
|
const disabled = currentCount <= minCount;
|
|
2337
2008
|
removeBtn.disabled = disabled;
|
|
2338
|
-
removeBtn.style.opacity = disabled ?
|
|
2339
|
-
removeBtn.style.pointerEvents = disabled ?
|
|
2009
|
+
removeBtn.style.opacity = disabled ? '0.5' : '1';
|
|
2010
|
+
removeBtn.style.pointerEvents = disabled ? 'none' : 'auto';
|
|
2340
2011
|
});
|
|
2341
2012
|
}
|
|
2342
2013
|
|
|
@@ -2347,12 +2018,10 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2347
2018
|
if (!state.config.readonly && values.length < maxCount) {
|
|
2348
2019
|
const addBtn = document.createElement("button");
|
|
2349
2020
|
addBtn.type = "button";
|
|
2350
|
-
addBtn.className =
|
|
2351
|
-
|
|
2352
|
-
addBtn.textContent = `+ Add ${element.label || "Selection"}`;
|
|
2021
|
+
addBtn.className = "add-select-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
2022
|
+
addBtn.textContent = `+ Add ${element.label || 'Selection'}`;
|
|
2353
2023
|
addBtn.onclick = () => {
|
|
2354
|
-
const defaultValue =
|
|
2355
|
-
element.default || element.options?.[0]?.value || "";
|
|
2024
|
+
const defaultValue = element.default || (element.options?.[0]?.value || "");
|
|
2356
2025
|
values.push(defaultValue);
|
|
2357
2026
|
addSelectItem(defaultValue);
|
|
2358
2027
|
updateAddButton();
|
|
@@ -2363,7 +2032,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2363
2032
|
}
|
|
2364
2033
|
|
|
2365
2034
|
// Render initial items
|
|
2366
|
-
values.forEach(
|
|
2035
|
+
values.forEach(value => addSelectItem(value));
|
|
2367
2036
|
updateAddButton();
|
|
2368
2037
|
updateRemoveButtons();
|
|
2369
2038
|
|
|
@@ -2385,11 +2054,7 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2385
2054
|
const emptyState = document.createElement("div");
|
|
2386
2055
|
emptyState.className =
|
|
2387
2056
|
"aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2388
|
-
|
|
2389
|
-
const textDiv = document.createElement("div");
|
|
2390
|
-
textDiv.className = "text-center";
|
|
2391
|
-
textDiv.textContent = t("noFileSelected");
|
|
2392
|
-
emptyState.appendChild(textDiv);
|
|
2057
|
+
emptyState.innerHTML = `<div class="text-center">${t("noFileSelected")}</div>`;
|
|
2393
2058
|
wrapper.appendChild(emptyState);
|
|
2394
2059
|
}
|
|
2395
2060
|
} else {
|
|
@@ -2449,14 +2114,8 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2449
2114
|
// Add upload text
|
|
2450
2115
|
const uploadText = document.createElement("p");
|
|
2451
2116
|
uploadText.className = "text-xs text-gray-600 mt-2 text-center";
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
uploadSpan.className = "underline cursor-pointer";
|
|
2455
|
-
uploadSpan.textContent = t("uploadText");
|
|
2456
|
-
uploadSpan.onclick = () => picker.click();
|
|
2457
|
-
|
|
2458
|
-
uploadText.appendChild(uploadSpan);
|
|
2459
|
-
uploadText.appendChild(document.createTextNode(" " + t("dragDropTextSingle")));
|
|
2117
|
+
uploadText.innerHTML = `<span class="underline cursor-pointer">${t("uploadText")}</span> ${t("dragDropTextSingle")}`;
|
|
2118
|
+
uploadText.querySelector("span").onclick = () => picker.click();
|
|
2460
2119
|
fileWrapper.appendChild(uploadText);
|
|
2461
2120
|
|
|
2462
2121
|
// Add hint
|
|
@@ -2515,29 +2174,14 @@ function handleInitialFileData(
|
|
|
2515
2174
|
}
|
|
2516
2175
|
|
|
2517
2176
|
function setEmptyFileContainer(fileContainer) {
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
svg.setAttribute("fill", "currentColor");
|
|
2527
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
2528
|
-
|
|
2529
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2530
|
-
path.setAttribute("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");
|
|
2531
|
-
|
|
2532
|
-
svg.appendChild(path);
|
|
2533
|
-
|
|
2534
|
-
const textDiv = document.createElement("div");
|
|
2535
|
-
textDiv.className = "text-sm text-center";
|
|
2536
|
-
textDiv.textContent = t("clickDragText");
|
|
2537
|
-
|
|
2538
|
-
wrapper.appendChild(svg);
|
|
2539
|
-
wrapper.appendChild(textDiv);
|
|
2540
|
-
fileContainer.appendChild(wrapper);
|
|
2177
|
+
fileContainer.innerHTML = `
|
|
2178
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2179
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
2180
|
+
<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"/>
|
|
2181
|
+
</svg>
|
|
2182
|
+
<div class="text-sm text-center">${t("clickDragText")}</div>
|
|
2183
|
+
</div>
|
|
2184
|
+
`;
|
|
2541
2185
|
}
|
|
2542
2186
|
|
|
2543
2187
|
function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
@@ -2554,16 +2198,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2554
2198
|
resultsWrapper.appendChild(filePreview);
|
|
2555
2199
|
});
|
|
2556
2200
|
} else {
|
|
2557
|
-
|
|
2558
|
-
const emptyDiv = document.createElement("div");
|
|
2559
|
-
emptyDiv.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2560
|
-
|
|
2561
|
-
const textDiv = document.createElement("div");
|
|
2562
|
-
textDiv.className = "text-center";
|
|
2563
|
-
textDiv.textContent = t("noFilesSelected");
|
|
2564
|
-
|
|
2565
|
-
emptyDiv.appendChild(textDiv);
|
|
2566
|
-
resultsWrapper.appendChild(emptyDiv);
|
|
2201
|
+
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${t("noFilesSelected")}</div></div>`;
|
|
2567
2202
|
}
|
|
2568
2203
|
|
|
2569
2204
|
wrapper.appendChild(resultsWrapper);
|
|
@@ -2644,16 +2279,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2644
2279
|
resultsWrapper.appendChild(filePreview);
|
|
2645
2280
|
});
|
|
2646
2281
|
} else {
|
|
2647
|
-
|
|
2648
|
-
const emptyDiv = document.createElement("div");
|
|
2649
|
-
emptyDiv.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2650
|
-
|
|
2651
|
-
const textDiv = document.createElement("div");
|
|
2652
|
-
textDiv.className = "text-center";
|
|
2653
|
-
textDiv.textContent = t("noFilesSelected");
|
|
2654
|
-
|
|
2655
|
-
emptyDiv.appendChild(textDiv);
|
|
2656
|
-
resultsWrapper.appendChild(emptyDiv);
|
|
2282
|
+
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${t("noFilesSelected")}</div></div>`;
|
|
2657
2283
|
}
|
|
2658
2284
|
|
|
2659
2285
|
wrapper.appendChild(resultsWrapper);
|
|
@@ -2695,18 +2321,17 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2695
2321
|
// Show count and min/max info
|
|
2696
2322
|
const countInfo = document.createElement("div");
|
|
2697
2323
|
countInfo.className = "text-xs text-gray-500 mt-2";
|
|
2698
|
-
const countText = `${initialFiles.length} file${initialFiles.length !== 1 ?
|
|
2699
|
-
const minMaxText =
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
: "";
|
|
2324
|
+
const countText = `${initialFiles.length} file${initialFiles.length !== 1 ? 's' : ''}`;
|
|
2325
|
+
const minMaxText = minFiles > 0 || maxFiles < Infinity
|
|
2326
|
+
? ` (${minFiles}-${maxFiles} allowed)`
|
|
2327
|
+
: '';
|
|
2703
2328
|
countInfo.textContent = countText + minMaxText;
|
|
2704
2329
|
|
|
2705
2330
|
// Remove previous count info
|
|
2706
|
-
const existingCount = filesWrapper.querySelector(
|
|
2331
|
+
const existingCount = filesWrapper.querySelector('.file-count-info');
|
|
2707
2332
|
if (existingCount) existingCount.remove();
|
|
2708
2333
|
|
|
2709
|
-
countInfo.className +=
|
|
2334
|
+
countInfo.className += ' file-count-info';
|
|
2710
2335
|
filesWrapper.appendChild(countInfo);
|
|
2711
2336
|
};
|
|
2712
2337
|
|
|
@@ -2890,18 +2515,7 @@ function renderRepeatableGroup(element, ctx, itemsWrap, left, groupWrap) {
|
|
|
2890
2515
|
addBtn.type = "button";
|
|
2891
2516
|
addBtn.className =
|
|
2892
2517
|
"w-full py-2 px-4 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 flex items-center justify-center mt-3";
|
|
2893
|
-
|
|
2894
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2895
|
-
svg.setAttribute("class", "w-4 h-4 mr-2");
|
|
2896
|
-
svg.setAttribute("fill", "currentColor");
|
|
2897
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
2898
|
-
|
|
2899
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2900
|
-
path.setAttribute("d", "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z");
|
|
2901
|
-
|
|
2902
|
-
svg.appendChild(path);
|
|
2903
|
-
addBtn.appendChild(svg);
|
|
2904
|
-
addBtn.appendChild(document.createTextNode(t("addElement")));
|
|
2518
|
+
addBtn.innerHTML = `<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>${t("addElement")}`;
|
|
2905
2519
|
groupWrap.appendChild(addBtn);
|
|
2906
2520
|
}
|
|
2907
2521
|
|
|
@@ -2909,16 +2523,7 @@ function renderRepeatableGroup(element, ctx, itemsWrap, left, groupWrap) {
|
|
|
2909
2523
|
if (state.config.readonly) return;
|
|
2910
2524
|
const n = countItems();
|
|
2911
2525
|
if (addBtn) addBtn.disabled = n >= max;
|
|
2912
|
-
|
|
2913
|
-
clear(left);
|
|
2914
|
-
const labelSpan = document.createElement("span");
|
|
2915
|
-
labelSpan.textContent = element.label || element.key;
|
|
2916
|
-
const countSpan = document.createElement("span");
|
|
2917
|
-
countSpan.className = "text-slate-500 dark:text-slate-400 text-xs";
|
|
2918
|
-
countSpan.textContent = `[${n} / ${max === Infinity ? "∞" : max}, min=${min}]`;
|
|
2919
|
-
left.appendChild(labelSpan);
|
|
2920
|
-
left.appendChild(document.createTextNode(" "));
|
|
2921
|
-
left.appendChild(countSpan);
|
|
2526
|
+
left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-slate-500 dark:text-slate-400 text-xs">[${n} / ${max === Infinity ? "∞" : max}, min=${min}]</span>`;
|
|
2922
2527
|
};
|
|
2923
2528
|
|
|
2924
2529
|
if (pre && pre.length) {
|
|
@@ -2933,10 +2538,7 @@ function renderRepeatableGroup(element, ctx, itemsWrap, left, groupWrap) {
|
|
|
2933
2538
|
addBtn.addEventListener("click", () => addItem(null));
|
|
2934
2539
|
} else {
|
|
2935
2540
|
// In readonly mode, just show the label without count controls
|
|
2936
|
-
|
|
2937
|
-
const labelSpan = document.createElement("span");
|
|
2938
|
-
labelSpan.textContent = element.label || element.key;
|
|
2939
|
-
left.appendChild(labelSpan);
|
|
2541
|
+
left.innerHTML = `<span>${element.label || element.key}</span>`;
|
|
2940
2542
|
}
|
|
2941
2543
|
}
|
|
2942
2544
|
|
|
@@ -2953,10 +2555,7 @@ function renderSingleGroup(element, ctx, itemsWrap, left, groupWrap) {
|
|
|
2953
2555
|
}
|
|
2954
2556
|
});
|
|
2955
2557
|
groupWrap.appendChild(itemsWrap);
|
|
2956
|
-
|
|
2957
|
-
const labelSpan = document.createElement("span");
|
|
2958
|
-
labelSpan.textContent = element.label || element.key;
|
|
2959
|
-
left.appendChild(labelSpan);
|
|
2558
|
+
left.innerHTML = `<span>${element.label || element.key}</span>`;
|
|
2960
2559
|
}
|
|
2961
2560
|
|
|
2962
2561
|
function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
@@ -2989,10 +2588,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
2989
2588
|
}
|
|
2990
2589
|
});
|
|
2991
2590
|
containerWrap.appendChild(itemsWrap);
|
|
2992
|
-
|
|
2993
|
-
const labelSpan = document.createElement("span");
|
|
2994
|
-
labelSpan.textContent = element.label || element.key;
|
|
2995
|
-
left.appendChild(labelSpan);
|
|
2591
|
+
left.innerHTML = `<span>${element.label || element.key}</span>`;
|
|
2996
2592
|
|
|
2997
2593
|
wrapper.appendChild(containerWrap);
|
|
2998
2594
|
}
|
|
@@ -3043,8 +2639,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3043
2639
|
prefill: {},
|
|
3044
2640
|
};
|
|
3045
2641
|
const item = document.createElement("div");
|
|
3046
|
-
item.className =
|
|
3047
|
-
"containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2642
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
3048
2643
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3049
2644
|
|
|
3050
2645
|
element.elements.forEach((child) => {
|
|
@@ -3083,16 +2678,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3083
2678
|
addBtn.disabled = currentCount >= max;
|
|
3084
2679
|
addBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
3085
2680
|
}
|
|
3086
|
-
|
|
3087
|
-
clear(left);
|
|
3088
|
-
const labelSpan = document.createElement("span");
|
|
3089
|
-
labelSpan.textContent = element.label || element.key;
|
|
3090
|
-
const countSpan = document.createElement("span");
|
|
3091
|
-
countSpan.className = "text-sm text-gray-500";
|
|
3092
|
-
countSpan.textContent = `(${currentCount}/${max === Infinity ? "∞" : max})`;
|
|
3093
|
-
left.appendChild(labelSpan);
|
|
3094
|
-
left.appendChild(document.createTextNode(" "));
|
|
3095
|
-
left.appendChild(countSpan);
|
|
2681
|
+
left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? '∞' : max})</span>`;
|
|
3096
2682
|
};
|
|
3097
2683
|
|
|
3098
2684
|
if (!state.config.readonly) {
|
|
@@ -3107,8 +2693,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3107
2693
|
prefill: prefillObj || {},
|
|
3108
2694
|
};
|
|
3109
2695
|
const item = document.createElement("div");
|
|
3110
|
-
item.className =
|
|
3111
|
-
"containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2696
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
3112
2697
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3113
2698
|
|
|
3114
2699
|
element.elements.forEach((child) => {
|
|
@@ -3146,8 +2731,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3146
2731
|
prefill: {},
|
|
3147
2732
|
};
|
|
3148
2733
|
const item = document.createElement("div");
|
|
3149
|
-
item.className =
|
|
3150
|
-
"containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2734
|
+
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
3151
2735
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3152
2736
|
|
|
3153
2737
|
element.elements.forEach((child) => {
|
|
@@ -3213,29 +2797,17 @@ function renderFilePreviewReadonly(resourceId, fileName) {
|
|
|
3213
2797
|
try {
|
|
3214
2798
|
const thumbnailUrl = state.config.getThumbnail(resourceId);
|
|
3215
2799
|
if (thumbnailUrl) {
|
|
3216
|
-
|
|
3217
|
-
img.src = thumbnailUrl;
|
|
3218
|
-
img.alt = actualFileName || "";
|
|
3219
|
-
img.className = "w-full h-auto";
|
|
3220
|
-
clear(previewContainer);
|
|
3221
|
-
previewContainer.appendChild(img);
|
|
2800
|
+
previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${actualFileName}" class="w-full h-auto">`;
|
|
3222
2801
|
} else {
|
|
3223
2802
|
// Fallback to icon if getThumbnail returns null/undefined
|
|
3224
|
-
|
|
3225
|
-
previewContainer.appendChild(
|
|
3226
|
-
createPreviewElement("🖼️", actualFileName),
|
|
3227
|
-
);
|
|
2803
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🖼️</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3228
2804
|
}
|
|
3229
2805
|
} catch (error) {
|
|
3230
2806
|
console.warn("getThumbnail failed for", resourceId, error);
|
|
3231
|
-
|
|
3232
|
-
previewContainer.appendChild(
|
|
3233
|
-
createPreviewElement("🖼️", actualFileName),
|
|
3234
|
-
);
|
|
2807
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🖼️</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3235
2808
|
}
|
|
3236
2809
|
} else {
|
|
3237
|
-
|
|
3238
|
-
previewContainer.appendChild(createPreviewElement("🖼️", actualFileName));
|
|
2810
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🖼️</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3239
2811
|
}
|
|
3240
2812
|
} else if (isVideo) {
|
|
3241
2813
|
// Video preview - try getThumbnail for video URL
|
|
@@ -3243,76 +2815,34 @@ function renderFilePreviewReadonly(resourceId, fileName) {
|
|
|
3243
2815
|
try {
|
|
3244
2816
|
const videoUrl = state.config.getThumbnail(resourceId);
|
|
3245
2817
|
if (videoUrl) {
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
video.appendChild(
|
|
3262
|
-
document.createTextNode("Ваш браузер не поддерживает видео."),
|
|
3263
|
-
);
|
|
3264
|
-
|
|
3265
|
-
const overlay = document.createElement("div");
|
|
3266
|
-
overlay.className =
|
|
3267
|
-
"absolute inset-0 bg-black bg-opacity-20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none";
|
|
3268
|
-
|
|
3269
|
-
const overlayContent = document.createElement("div");
|
|
3270
|
-
overlayContent.className = "bg-white bg-opacity-90 rounded-full p-3";
|
|
3271
|
-
|
|
3272
|
-
const svg = document.createElementNS(
|
|
3273
|
-
"http://www.w3.org/2000/svg",
|
|
3274
|
-
"svg",
|
|
3275
|
-
);
|
|
3276
|
-
svg.setAttribute("class", "w-8 h-8 text-gray-800");
|
|
3277
|
-
svg.setAttribute("fill", "currentColor");
|
|
3278
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
3279
|
-
|
|
3280
|
-
const path = document.createElementNS(
|
|
3281
|
-
"http://www.w3.org/2000/svg",
|
|
3282
|
-
"path",
|
|
3283
|
-
);
|
|
3284
|
-
path.setAttribute("d", "M8 5v14l11-7z");
|
|
3285
|
-
|
|
3286
|
-
svg.appendChild(path);
|
|
3287
|
-
overlayContent.appendChild(svg);
|
|
3288
|
-
overlay.appendChild(overlayContent);
|
|
3289
|
-
|
|
3290
|
-
wrapper.appendChild(video);
|
|
3291
|
-
wrapper.appendChild(overlay);
|
|
3292
|
-
|
|
3293
|
-
clear(previewContainer);
|
|
3294
|
-
previewContainer.appendChild(wrapper);
|
|
2818
|
+
previewContainer.innerHTML = `
|
|
2819
|
+
<div class="relative group">
|
|
2820
|
+
<video class="w-full h-auto" controls preload="auto" muted>
|
|
2821
|
+
<source src="${videoUrl}" type="${meta?.type || "video/mp4"}">
|
|
2822
|
+
Ваш браузер не поддерживает видео.
|
|
2823
|
+
</video>
|
|
2824
|
+
<div class="absolute inset-0 bg-black bg-opacity-20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
|
|
2825
|
+
<div class="bg-white bg-opacity-90 rounded-full p-3">
|
|
2826
|
+
<svg class="w-8 h-8 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
2827
|
+
<path d="M8 5v14l11-7z"/>
|
|
2828
|
+
</svg>
|
|
2829
|
+
</div>
|
|
2830
|
+
</div>
|
|
2831
|
+
</div>
|
|
2832
|
+
`;
|
|
3295
2833
|
} else {
|
|
3296
|
-
|
|
3297
|
-
previewContainer.appendChild(
|
|
3298
|
-
createPreviewElement("🎥", actualFileName),
|
|
3299
|
-
);
|
|
2834
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🎥</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3300
2835
|
}
|
|
3301
2836
|
} catch (error) {
|
|
3302
2837
|
console.warn("getThumbnail failed for video", resourceId, error);
|
|
3303
|
-
|
|
3304
|
-
previewContainer.appendChild(
|
|
3305
|
-
createPreviewElement("🎥", actualFileName),
|
|
3306
|
-
);
|
|
2838
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🎥</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3307
2839
|
}
|
|
3308
2840
|
} else {
|
|
3309
|
-
|
|
3310
|
-
previewContainer.appendChild(createPreviewElement("🎥", actualFileName));
|
|
2841
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🎥</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3311
2842
|
}
|
|
3312
2843
|
} else {
|
|
3313
2844
|
// Other file types
|
|
3314
|
-
|
|
3315
|
-
previewContainer.appendChild(createPreviewElement("📁", actualFileName));
|
|
2845
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">📁</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
3316
2846
|
}
|
|
3317
2847
|
|
|
3318
2848
|
// File name
|
|
@@ -3478,8 +3008,6 @@ function saveDraft() {
|
|
|
3478
3008
|
|
|
3479
3009
|
function clearForm() {
|
|
3480
3010
|
if (state.formRoot) {
|
|
3481
|
-
// Clean up any existing object URLs before clearing form
|
|
3482
|
-
revokeAllObjectURLs();
|
|
3483
3011
|
clear(state.formRoot);
|
|
3484
3012
|
}
|
|
3485
3013
|
}
|