updraft / components /image-drop-zone.js
Nicholas Celestin
Build update — 2026-05-22T18:34:00.912Z
3f22414
Raw
History Blame Contribute Delete
3 kB
/**
* <image-drop-zone> — file picker + drag-and-drop image loader.
*
* Events:
* image-loaded — detail: { image: HTMLImageElement }
*/
import { morph } from 'lib/morph';
class ImageDropZone extends HTMLElement {
connectedCallback() {
this.classList.add('drop-zone');
this.#render();
this.addEventListener('click', e => {
if (e.target.closest('.drop-zone-area')) this.querySelector('input[type="file"]').click();
});
this.addEventListener('dragover', e => {
if (e.target.closest('.drop-zone-area')) { e.preventDefault(); e.target.closest('.drop-zone-area').classList.add('dragover'); }
});
this.addEventListener('dragleave', e => {
if (e.target.closest('.drop-zone-area')) e.target.closest('.drop-zone-area').classList.remove('dragover');
});
this.addEventListener('drop', e => {
const area = e.target.closest('.drop-zone-area');
if (!area) return;
e.preventDefault();
area.classList.remove('dragover');
if (e.dataTransfer.files.length) this.#handleFile(e.dataTransfer.files[0]);
});
this.addEventListener('change', e => {
if (e.target.matches('input[type="file"]') && e.target.files.length) this.#handleFile(e.target.files[0]);
});
document.addEventListener('paste', e => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
this.#handleFile(item.getAsFile());
return;
}
}
});
}
#handleFile(file) {
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
const img = new Image();
img.onload = () => {
this.dispatchEvent(new CustomEvent('image-loaded', { bubbles: true, detail: { image: img } }));
};
img.src = reader.result;
};
reader.readAsDataURL(file);
}
show() {
this.style.display = '';
const input = this.querySelector('input[type="file"]');
if (input) input.value = '';
}
hide() { this.style.display = 'none'; }
#render() {
morph(this, `
<style>
.drop-zone .drop-zone-area {
border: 2px dashed var(--pico-muted-border-color, #444);
border-radius: 8px; padding: 3rem; text-align: center;
color: var(--pico-muted-color, #666); font-size: 0.9rem;
cursor: pointer; transition: border-color 0.2s;
}
.drop-zone .drop-zone-area.dragover {
border-color: var(--pico-primary, #4c8);
color: var(--pico-primary, #4c8);
}
</style>
<input type="file" accept="image/*" hidden>
<div class="drop-zone-area">
<i class="fas fa-cloud-upload-alt" style="font-size:1.5rem; display:block; margin-bottom:0.5rem"></i>
Drop an image here, paste from clipboard, or click to browse
</div>
`);
}
}
customElements.define('image-drop-zone', ImageDropZone);