@dmitryvim/form-builder 0.1.15 → 0.1.17
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/form-builder.js +154 -74
- package/package.json +1 -1
package/dist/form-builder.js
CHANGED
|
@@ -87,35 +87,45 @@ function validateSchema(schema) {
|
|
|
87
87
|
|
|
88
88
|
// Form rendering
|
|
89
89
|
function renderForm(schema, prefill) {
|
|
90
|
+
console.log('🔧 FormBuilder.renderForm called with:', { schema, prefill });
|
|
91
|
+
|
|
90
92
|
const errors = validateSchema(schema);
|
|
91
93
|
if (errors.length > 0) {
|
|
92
94
|
console.error('Schema validation errors:', errors);
|
|
93
95
|
return;
|
|
94
96
|
}
|
|
97
|
+
console.log('✅ Schema validation passed');
|
|
95
98
|
|
|
96
99
|
state.schema = schema;
|
|
97
100
|
if (!state.formRoot) {
|
|
98
101
|
console.error('No form root element set. Call setFormRoot() first.');
|
|
99
102
|
return;
|
|
100
103
|
}
|
|
104
|
+
console.log('✅ FormRoot is set:', state.formRoot);
|
|
101
105
|
|
|
102
106
|
clear(state.formRoot);
|
|
103
107
|
|
|
104
108
|
const formEl = document.createElement('div');
|
|
105
109
|
formEl.className = 'space-y-6';
|
|
106
110
|
|
|
107
|
-
schema.elements.
|
|
111
|
+
console.log(`🔧 Processing ${schema.elements.length} schema elements`);
|
|
112
|
+
schema.elements.forEach((element, index) => {
|
|
113
|
+
console.log(`🔧 Rendering element ${index}:`, element);
|
|
108
114
|
const block = renderElement(element, {
|
|
109
115
|
path: '',
|
|
110
116
|
prefill: prefill || {}
|
|
111
117
|
});
|
|
118
|
+
console.log(`✅ Element ${index} rendered:`, block);
|
|
112
119
|
formEl.appendChild(block);
|
|
113
120
|
});
|
|
114
121
|
|
|
115
122
|
state.formRoot.appendChild(formEl);
|
|
123
|
+
console.log(`✅ Form rendered with ${schema.elements.length} elements`);
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
function renderElement(element, ctx) {
|
|
127
|
+
console.log(`🔧 renderElement called:`, { element, ctx });
|
|
128
|
+
|
|
119
129
|
const wrapper = document.createElement('div');
|
|
120
130
|
wrapper.className = 'mb-6';
|
|
121
131
|
|
|
@@ -180,17 +190,21 @@ function renderElement(element, ctx) {
|
|
|
180
190
|
|
|
181
191
|
case 'file':
|
|
182
192
|
// TODO: Extract to renderFileElement() function
|
|
193
|
+
console.log(`🔧 Rendering file element '${element.key}':`, { readonly: state.config.readonly, prefill: ctx.prefill[element.key], element });
|
|
183
194
|
if (state.config.readonly) {
|
|
184
195
|
// Readonly mode: use common preview function
|
|
185
196
|
const initial = ctx.prefill[element.key] || element.default;
|
|
197
|
+
console.log(`🔧 File initial value:`, initial);
|
|
186
198
|
if (initial) {
|
|
187
199
|
const filePreview = renderFilePreviewReadonly(initial);
|
|
188
200
|
wrapper.appendChild(filePreview);
|
|
201
|
+
console.log(`✅ File preview rendered for '${element.key}'`);
|
|
189
202
|
} else {
|
|
190
203
|
const emptyState = document.createElement('div');
|
|
191
204
|
emptyState.className = 'aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500';
|
|
192
205
|
emptyState.innerHTML = '<div class="text-center">Нет файла</div>';
|
|
193
206
|
wrapper.appendChild(emptyState);
|
|
207
|
+
console.log(`❌ No file data for '${element.key}' - showing empty state`);
|
|
194
208
|
}
|
|
195
209
|
} else {
|
|
196
210
|
// Edit mode: normal file input
|
|
@@ -212,7 +226,7 @@ function renderElement(element, ctx) {
|
|
|
212
226
|
|
|
213
227
|
const initial = ctx.prefill[element.key] || element.default;
|
|
214
228
|
if (initial) {
|
|
215
|
-
renderFilePreview(fileContainer, initial, initial, '');
|
|
229
|
+
renderFilePreview(fileContainer, initial, initial, '', false).catch(console.error);
|
|
216
230
|
} else {
|
|
217
231
|
fileContainer.innerHTML = `
|
|
218
232
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
@@ -566,6 +580,28 @@ async function renderFilePreview(container, resourceId, fileName, fileType, isRe
|
|
|
566
580
|
// Non-image file
|
|
567
581
|
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">' + fileName + '</div></div>';
|
|
568
582
|
}
|
|
583
|
+
|
|
584
|
+
// Add delete button for edit mode
|
|
585
|
+
if (!isReadonly) {
|
|
586
|
+
addDeleteButton(container, () => {
|
|
587
|
+
// Clear the file
|
|
588
|
+
state.resourceIndex.delete(resourceId);
|
|
589
|
+
// Update hidden input
|
|
590
|
+
const hiddenInput = container.parentElement.querySelector('input[type="hidden"]');
|
|
591
|
+
if (hiddenInput) {
|
|
592
|
+
hiddenInput.value = '';
|
|
593
|
+
}
|
|
594
|
+
// Clear preview and show placeholder
|
|
595
|
+
container.innerHTML = `
|
|
596
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
597
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
598
|
+
<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"/>
|
|
599
|
+
</svg>
|
|
600
|
+
<div class="text-sm text-center">Нажмите или перетащите файл</div>
|
|
601
|
+
</div>
|
|
602
|
+
`;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
569
605
|
} else if (state.config.getThumbnail) {
|
|
570
606
|
// Try to get thumbnail from config for uploaded files
|
|
571
607
|
try {
|
|
@@ -581,33 +617,35 @@ async function renderFilePreview(container, resourceId, fileName, fileType, isRe
|
|
|
581
617
|
console.warn('Thumbnail loading failed:', error);
|
|
582
618
|
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">' + fileName + '</div></div>';
|
|
583
619
|
}
|
|
620
|
+
|
|
621
|
+
// Add delete button for edit mode
|
|
622
|
+
if (!isReadonly) {
|
|
623
|
+
addDeleteButton(container, () => {
|
|
624
|
+
// Clear the file
|
|
625
|
+
state.resourceIndex.delete(resourceId);
|
|
626
|
+
// Update hidden input
|
|
627
|
+
const hiddenInput = container.parentElement.querySelector('input[type="hidden"]');
|
|
628
|
+
if (hiddenInput) {
|
|
629
|
+
hiddenInput.value = '';
|
|
630
|
+
}
|
|
631
|
+
// Clear preview and show placeholder
|
|
632
|
+
container.innerHTML = `
|
|
633
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
634
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
635
|
+
<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"/>
|
|
636
|
+
</svg>
|
|
637
|
+
<div class="text-sm text-center">Нажмите или перетащите файл</div>
|
|
638
|
+
</div>
|
|
639
|
+
`;
|
|
640
|
+
});
|
|
641
|
+
}
|
|
584
642
|
} else {
|
|
585
643
|
// No file and no getThumbnail config - fallback
|
|
586
644
|
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">' + fileName + '</div></div>';
|
|
587
645
|
}
|
|
588
646
|
|
|
589
|
-
// Add
|
|
590
|
-
if (
|
|
591
|
-
const overlay = document.createElement('div');
|
|
592
|
-
overlay.className = 'absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center';
|
|
593
|
-
const buttonContainer = document.createElement('div');
|
|
594
|
-
buttonContainer.className = 'flex gap-2';
|
|
595
|
-
|
|
596
|
-
const removeBtn = document.createElement('button');
|
|
597
|
-
removeBtn.className = 'bg-red-600 text-white px-2 py-1 rounded text-xs';
|
|
598
|
-
removeBtn.textContent = 'Удалить';
|
|
599
|
-
removeBtn.onclick = (e) => {
|
|
600
|
-
e.stopPropagation();
|
|
601
|
-
const hiddenInput = container.parentElement.querySelector('input[type="hidden"]');
|
|
602
|
-
if (hiddenInput) hiddenInput.value = '';
|
|
603
|
-
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">Click or drop to upload</div></div>';
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
buttonContainer.appendChild(removeBtn);
|
|
607
|
-
overlay.appendChild(buttonContainer);
|
|
608
|
-
container.appendChild(overlay);
|
|
609
|
-
} else if (isReadonly && state.config.downloadFile) {
|
|
610
|
-
// Add click handler for download in readonly mode
|
|
647
|
+
// Add click handler for download in readonly mode
|
|
648
|
+
if (isReadonly && state.config.downloadFile) {
|
|
611
649
|
container.style.cursor = 'pointer';
|
|
612
650
|
container.onclick = () => {
|
|
613
651
|
if (state.config.downloadFile) {
|
|
@@ -620,66 +658,83 @@ async function renderFilePreview(container, resourceId, fileName, fileType, isRe
|
|
|
620
658
|
function renderResourcePills(container, rids, onRemove) {
|
|
621
659
|
clear(container);
|
|
622
660
|
|
|
623
|
-
// Show
|
|
624
|
-
if
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
</div>
|
|
632
|
-
<div class="aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors">
|
|
633
|
-
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
|
|
634
|
-
<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"/>
|
|
635
|
-
</svg>
|
|
636
|
-
</div>
|
|
637
|
-
<div class="aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors">
|
|
638
|
-
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
|
|
639
|
-
<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"/>
|
|
640
|
-
</svg>
|
|
641
|
-
</div>
|
|
642
|
-
<div class="aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors">
|
|
643
|
-
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
|
|
644
|
-
<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"/>
|
|
645
|
-
</svg>
|
|
646
|
-
</div>
|
|
647
|
-
</div>
|
|
648
|
-
<div class="text-center text-xs text-gray-600">
|
|
649
|
-
<span class="underline cursor-pointer">Загрузите</span> или перетащите файлы
|
|
650
|
-
</div>
|
|
651
|
-
`;
|
|
652
|
-
// Add click handler to the entire placeholder
|
|
653
|
-
container.onclick = () => {
|
|
654
|
-
const fileInput = container.parentElement?.querySelector('input[type="file"]');
|
|
655
|
-
if (fileInput) fileInput.click();
|
|
656
|
-
};
|
|
661
|
+
// Show initial placeholder only if this is the first render (no previous grid)
|
|
662
|
+
// Check if container already has grid class to determine if this is initial render
|
|
663
|
+
const isInitialRender = !container.classList.contains('grid');
|
|
664
|
+
|
|
665
|
+
if ((!rids || rids.length === 0) && isInitialRender) {
|
|
666
|
+
// Create grid container
|
|
667
|
+
const gridContainer = document.createElement('div');
|
|
668
|
+
gridContainer.className = 'grid grid-cols-4 gap-3 mb-3';
|
|
657
669
|
|
|
658
|
-
//
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
670
|
+
// Create 4 placeholder slots
|
|
671
|
+
for (let i = 0; i < 4; i++) {
|
|
672
|
+
const slot = document.createElement('div');
|
|
673
|
+
slot.className = 'aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors';
|
|
674
|
+
|
|
675
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
676
|
+
svg.setAttribute('class', 'w-12 h-12 text-gray-400');
|
|
677
|
+
svg.setAttribute('fill', 'currentColor');
|
|
678
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
679
|
+
|
|
680
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
681
|
+
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');
|
|
682
|
+
|
|
683
|
+
svg.appendChild(path);
|
|
684
|
+
slot.appendChild(svg);
|
|
685
|
+
|
|
686
|
+
// Add click handler to each slot
|
|
687
|
+
slot.onclick = () => {
|
|
688
|
+
// Look for file input in the files wrapper (go up from list -> filesContainer -> filesWrapper)
|
|
689
|
+
const filesWrapper = container.closest('.space-y-2');
|
|
690
|
+
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
664
691
|
if (fileInput) fileInput.click();
|
|
665
692
|
};
|
|
693
|
+
|
|
694
|
+
gridContainer.appendChild(slot);
|
|
666
695
|
}
|
|
696
|
+
|
|
697
|
+
// Create text container
|
|
698
|
+
const textContainer = document.createElement('div');
|
|
699
|
+
textContainer.className = 'text-center text-xs text-gray-600';
|
|
700
|
+
|
|
701
|
+
const uploadLink = document.createElement('span');
|
|
702
|
+
uploadLink.className = 'underline cursor-pointer';
|
|
703
|
+
uploadLink.textContent = 'Загрузите';
|
|
704
|
+
uploadLink.onclick = (e) => {
|
|
705
|
+
e.stopPropagation();
|
|
706
|
+
// Look for file input in the files wrapper (go up from list -> filesContainer -> filesWrapper)
|
|
707
|
+
const filesWrapper = container.closest('.space-y-2');
|
|
708
|
+
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
709
|
+
if (fileInput) fileInput.click();
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
textContainer.appendChild(uploadLink);
|
|
713
|
+
textContainer.appendChild(document.createTextNode(' или перетащите файлы'));
|
|
714
|
+
|
|
715
|
+
// Clear and append
|
|
716
|
+
container.appendChild(gridContainer);
|
|
717
|
+
container.appendChild(textContainer);
|
|
667
718
|
return;
|
|
668
719
|
}
|
|
669
720
|
|
|
670
|
-
//
|
|
721
|
+
// Always show files grid if we have files OR if this was already a grid
|
|
722
|
+
// This prevents shrinking when deleting the last file
|
|
671
723
|
container.className = 'grid grid-cols-4 gap-3 mt-2';
|
|
672
724
|
|
|
673
725
|
// Calculate how many slots we need (at least 4, then expand by rows of 4)
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
726
|
+
const currentImagesCount = rids ? rids.length : 0;
|
|
727
|
+
// Calculate rows needed: always show an extra slot for adding next file
|
|
728
|
+
// 0-3 files → 1 row (4 slots), 4-7 files → 2 rows (8 slots), 8-11 files → 3 rows (12 slots)
|
|
729
|
+
const rowsNeeded = Math.floor(currentImagesCount / 4) + 1;
|
|
730
|
+
const slotsNeeded = rowsNeeded * 4;
|
|
731
|
+
|
|
677
732
|
|
|
678
733
|
// Add all slots (filled and empty)
|
|
679
734
|
for (let i = 0; i < slotsNeeded; i++) {
|
|
680
735
|
const slot = document.createElement('div');
|
|
681
736
|
|
|
682
|
-
if (i < rids.length) {
|
|
737
|
+
if (rids && i < rids.length) {
|
|
683
738
|
// Filled slot with image preview
|
|
684
739
|
const rid = rids[i];
|
|
685
740
|
const meta = state.resourceIndex.get(rid);
|
|
@@ -711,14 +766,14 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
711
766
|
slot.appendChild(img);
|
|
712
767
|
} else {
|
|
713
768
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
714
|
-
<svg class="w-
|
|
769
|
+
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
715
770
|
<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
771
|
</svg>
|
|
717
772
|
</div>`;
|
|
718
773
|
}
|
|
719
774
|
} else {
|
|
720
775
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
721
|
-
<svg class="w-
|
|
776
|
+
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
722
777
|
<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
778
|
</svg>
|
|
724
779
|
</div>`;
|
|
@@ -749,9 +804,11 @@ function renderResourcePills(container, rids, onRemove) {
|
|
|
749
804
|
} else {
|
|
750
805
|
// Empty slot placeholder
|
|
751
806
|
slot.className = 'aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors';
|
|
752
|
-
slot.innerHTML = '<svg class="w-
|
|
807
|
+
slot.innerHTML = '<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>';
|
|
753
808
|
slot.onclick = () => {
|
|
754
|
-
|
|
809
|
+
// Look for file input in the files wrapper (go up from list -> filesContainer -> filesWrapper)
|
|
810
|
+
const filesWrapper = container.closest('.space-y-2');
|
|
811
|
+
const fileInput = filesWrapper?.querySelector('input[type="file"]');
|
|
755
812
|
if (fileInput) fileInput.click();
|
|
756
813
|
};
|
|
757
814
|
}
|
|
@@ -806,7 +863,7 @@ async function handleFileSelect(file, container, fieldName) {
|
|
|
806
863
|
}
|
|
807
864
|
hiddenInput.value = rid;
|
|
808
865
|
|
|
809
|
-
renderFilePreview(container, rid, file.name, file.type);
|
|
866
|
+
renderFilePreview(container, rid, file.name, file.type, false).catch(console.error);
|
|
810
867
|
}
|
|
811
868
|
|
|
812
869
|
function setupDragAndDrop(element, dropHandler) {
|
|
@@ -827,6 +884,29 @@ function setupDragAndDrop(element, dropHandler) {
|
|
|
827
884
|
});
|
|
828
885
|
}
|
|
829
886
|
|
|
887
|
+
function addDeleteButton(container, onDelete) {
|
|
888
|
+
// Remove existing overlay if any
|
|
889
|
+
const existingOverlay = container.querySelector('.delete-overlay');
|
|
890
|
+
if (existingOverlay) {
|
|
891
|
+
existingOverlay.remove();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Create overlay with center delete button (like in files)
|
|
895
|
+
const overlay = document.createElement('div');
|
|
896
|
+
overlay.className = 'delete-overlay absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center';
|
|
897
|
+
|
|
898
|
+
const deleteBtn = document.createElement('button');
|
|
899
|
+
deleteBtn.className = 'bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors';
|
|
900
|
+
deleteBtn.textContent = 'Удалить';
|
|
901
|
+
deleteBtn.onclick = (e) => {
|
|
902
|
+
e.stopPropagation();
|
|
903
|
+
onDelete();
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
overlay.appendChild(deleteBtn);
|
|
907
|
+
container.appendChild(overlay);
|
|
908
|
+
}
|
|
909
|
+
|
|
830
910
|
function showTooltip(tooltipId, button) {
|
|
831
911
|
const tooltip = document.getElementById(tooltipId);
|
|
832
912
|
const isCurrentlyVisible = !tooltip.classList.contains('hidden');
|