@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.
@@ -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
- // Create SVG icon safely
242
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
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 managed object URL for preview
483
- const videoUrl = createObjectURL(meta.file, resourceId);
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
- // Create video container safely
494
- clear(container);
495
-
496
- const wrapper = document.createElement("div");
497
- wrapper.className = "relative group h-full";
498
-
499
- const video = document.createElement("video");
500
- video.className = "w-full h-full object-contain";
501
- video.controls = true;
502
- video.preload = "auto";
503
- video.muted = true;
504
-
505
- const source = document.createElement("source");
506
- source.src = videoUrl;
507
- source.type = meta.type;
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.onclick = (e) => {
532
- e.stopPropagation();
533
- if (deps?.picker) {
534
- deps.picker.click();
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.onclick = (e) => {
458
+ const deleteBtn = container.querySelector(".delete-file-btn");
459
+ if (deleteBtn) {
460
+ deleteBtn.onclick = (e) => {
539
461
  e.stopPropagation();
540
- // Clear the file and revoke object URL
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
- // Create placeholder content safely
558
- clear(container);
559
-
560
- const wrapper = document.createElement("div");
561
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
562
-
563
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
564
- svg.setAttribute("class", "w-6 h-6 mb-2");
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 - create elements safely
583
- const wrapper = document.createElement("div");
584
- wrapper.className =
585
- "flex flex-col items-center justify-center h-full text-gray-400";
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 and revoke object URL
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 safely
615
- clear(container);
616
-
617
- const wrapper = document.createElement("div");
618
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
619
-
620
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
621
- svg.setAttribute("class", "w-6 h-6 mb-2");
622
- svg.setAttribute("fill", "currentColor");
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
- // Create elements safely
650
- const wrapper = document.createElement("div");
651
- wrapper.className =
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
- // Create elements safely
670
- const wrapper = document.createElement("div");
671
- wrapper.className =
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 and revoke object URL
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 safely
702
- clear(container);
703
-
704
- const wrapper = document.createElement("div");
705
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
706
-
707
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
708
- svg.setAttribute("class", "w-6 h-6 mb-2");
709
- svg.setAttribute("fill", "currentColor");
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 - create elements safely
728
- clear(container);
729
- container.appendChild(createPreviewElement("🖼️", fileName));
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("space-y-2")) {
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("space-y-2")) {
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("space-y-2")) {
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("space-y-2")) {
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
- // Create fallback placeholder safely
877
- const wrapper = document.createElement("div");
878
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
879
-
880
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
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
- // Create fallback placeholder safely
894
- const wrapper = document.createElement("div");
895
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
896
-
897
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
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 managed object URL for preview in thumbnail format
912
- const videoUrl = createObjectURL(meta.file, rid);
913
- // Create video thumbnail safely
914
- const wrapper = document.createElement("div");
915
- wrapper.className = "relative group h-full w-full";
916
-
917
- const video = document.createElement("video");
918
- video.className = "w-full h-full object-contain";
919
- video.preload = "metadata";
920
- video.muted = true;
921
-
922
- const source = document.createElement("source");
923
- source.src = videoUrl;
924
- source.type = meta.type;
925
-
926
- video.appendChild(source);
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
- // Create video thumbnail safely
953
- const wrapper = document.createElement("div");
954
- wrapper.className = "relative group h-full w-full";
955
-
956
- const video = document.createElement("video");
957
- video.className = "w-full h-full object-contain";
958
- video.preload = "metadata";
959
- video.muted = true;
960
-
961
- const source = document.createElement("source");
962
- source.src = videoUrl;
963
- source.type = meta.type;
964
-
965
- video.appendChild(source);
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
- // Create video placeholder safely
989
- const wrapper = document.createElement("div");
990
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
991
-
992
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
993
- svg.setAttribute("class", "w-8 h-8");
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
- // Create video placeholder safely
1012
- const wrapper = document.createElement("div");
1013
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
1014
-
1015
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1016
- svg.setAttribute("class", "w-8 h-8");
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
- // Create file placeholder safely
1035
- const wrapper = document.createElement("div");
1036
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
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
- // Create empty slot SVG safely
1073
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
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("space-y-2")) {
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("space-y-2")) {
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
- // Handle case where container might be detached due to video replacement
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((v) => v.trim() !== "");
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((v) => v !== null);
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((v) => v !== "");
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
- `input[type="file"][name="${fullKey}"]`,
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(".multiple-text-item");
1574
+ const items = container.querySelectorAll('.multiple-text-item');
1894
1575
  const currentCount = items.length;
1895
1576
  items.forEach((item) => {
1896
- let removeBtn = item.querySelector(".remove-item-btn");
1577
+ let removeBtn = item.querySelector('.remove-item-btn');
1897
1578
  if (!removeBtn) {
1898
- removeBtn = document.createElement("button");
1899
- removeBtn.type = "button";
1900
- removeBtn.className =
1901
- "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
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 ? "0.5" : "1";
1918
- removeBtn.style.pointerEvents = disabled ? "none" : "auto";
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
- "add-text-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
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((value) => addTextItem(value));
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(".multiple-textarea-item");
1704
+ const items = container.querySelectorAll('.multiple-textarea-item');
2027
1705
  const currentCount = items.length;
2028
1706
  items.forEach((item) => {
2029
- let removeBtn = item.querySelector(".remove-item-btn");
1707
+ let removeBtn = item.querySelector('.remove-item-btn');
2030
1708
  if (!removeBtn) {
2031
- removeBtn = document.createElement("button");
2032
- removeBtn.type = "button";
2033
- removeBtn.className =
2034
- "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
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 ? "0.5" : "1";
2051
- removeBtn.style.pointerEvents = disabled ? "none" : "auto";
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
- "add-textarea-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
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((value) => addTextareaItem(value));
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(".multiple-number-item");
1840
+ const items = container.querySelectorAll('.multiple-number-item');
2166
1841
  const currentCount = items.length;
2167
1842
  items.forEach((item) => {
2168
- let removeBtn = item.querySelector(".remove-item-btn");
1843
+ let removeBtn = item.querySelector('.remove-item-btn');
2169
1844
  if (!removeBtn) {
2170
- removeBtn = document.createElement("button");
2171
- removeBtn.type = "button";
2172
- removeBtn.className =
2173
- "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
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 ? "0.5" : "1";
2190
- removeBtn.style.pointerEvents = disabled ? "none" : "auto";
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
- "add-number-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
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((value) => addNumberItem(value));
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(".multiple-select-item");
1986
+ const items = container.querySelectorAll('.multiple-select-item');
2315
1987
  const currentCount = items.length;
2316
1988
  items.forEach((item) => {
2317
- let removeBtn = item.querySelector(".remove-item-btn");
1989
+ let removeBtn = item.querySelector('.remove-item-btn');
2318
1990
  if (!removeBtn) {
2319
- removeBtn = document.createElement("button");
2320
- removeBtn.type = "button";
2321
- removeBtn.className =
2322
- "remove-item-btn px-2 py-1 text-red-600 hover:bg-red-50 rounded";
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 ? "0.5" : "1";
2339
- removeBtn.style.pointerEvents = disabled ? "none" : "auto";
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
- "add-select-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
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((value) => addSelectItem(value));
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
- // Create empty state content safely
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
- // Create upload text content safely
2453
- const uploadSpan = document.createElement("span");
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
- // Create empty file container content safely
2519
- clear(fileContainer);
2520
-
2521
- const wrapper = document.createElement("div");
2522
- wrapper.className = "flex flex-col items-center justify-center h-full text-gray-400";
2523
-
2524
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2525
- svg.setAttribute("class", "w-6 h-6 mb-2");
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
- // Create empty state safely
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
- // Create empty state safely
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 ? "s" : ""}`;
2699
- const minMaxText =
2700
- minFiles > 0 || maxFiles < Infinity
2701
- ? ` (${minFiles}-${maxFiles} allowed)`
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(".file-count-info");
2331
+ const existingCount = filesWrapper.querySelector('.file-count-info');
2707
2332
  if (existingCount) existingCount.remove();
2708
2333
 
2709
- countInfo.className += " file-count-info";
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
- // Create add button content safely
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
- // Create elements safely
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
- clear(left);
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
- clear(left);
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
- clear(left);
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
- // Create elements safely
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
- const img = document.createElement("img");
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
- clear(previewContainer);
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
- clear(previewContainer);
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
- clear(previewContainer);
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
- // Create video elements safely
3247
- const wrapper = document.createElement("div");
3248
- wrapper.className = "relative group";
3249
-
3250
- const video = document.createElement("video");
3251
- video.className = "w-full h-auto";
3252
- video.controls = true;
3253
- video.preload = "auto";
3254
- video.muted = true;
3255
-
3256
- const source = document.createElement("source");
3257
- source.src = videoUrl;
3258
- source.type = meta?.type || "video/mp4";
3259
-
3260
- video.appendChild(source);
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
- clear(previewContainer);
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
- clear(previewContainer);
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
- clear(previewContainer);
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
- clear(previewContainer);
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
  }