This pattern bypasses the VS Code webview’s default drag handling by:
- Capturing drag events at the window level
- Calling
preventDefault()(and oftenstopImmediatePropagation()) - Showing a container-specific overlay
- Reading files from
dataTransfer.items/dataTransfer.files
const container = document.querySelector<HTMLElement>('[data-drop-target]')!
function isOverContainer(event: DragEvent): boolean {
const rect = container.getBoundingClientRect()
return (
event.clientX >= rect.left &&
event.clientX <= rect.right &&
event.clientY >= rect.top &&
event.clientY <= rect.bottom
)
}
function handleGlobalDragOver(event: DragEvent): void {
event.preventDefault()
event.stopImmediatePropagation()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy'
event.dataTransfer.effectAllowed = 'copy'
}
container.classList.toggle('is-dragging', isOverContainer(event))
}
function handleGlobalDrop(event: DragEvent): void {
event.preventDefault()
event.stopImmediatePropagation()
if (!isOverContainer(event)) {
container.classList.remove('is-dragging')
return
}
handleDrop(event)
}
window.addEventListener('dragover', handleGlobalDragOver, { capture: true })
window.addEventListener('drop', handleGlobalDrop, { capture: true }).drop-target {
position: relative;
}
.drop-target.is-dragging::after {
content: 'Drop to attach image';
position: absolute;
inset: 0;
display: grid;
place-items: center;
border: 2px dashed var(--accent);
background: rgba(0, 0, 0, 0.3);
color: white;
pointer-events: none;
}function getDroppedFiles(event: DragEvent): File[] {
const files: File[] = []
if (event.dataTransfer?.files?.length) {
files.push(...event.dataTransfer.files)
}
if (event.dataTransfer?.items?.length) {
for (const item of event.dataTransfer.items) {
if (item.kind !== 'file') continue
const file = item.getAsFile()
if (file) files.push(file)
}
}
return dedupeFiles(files)
}
function dedupeFiles(files: File[]): File[] {
const map = new Map<string, File>()
for (const file of files) {
const key = `${file.name}-${file.size}-${file.lastModified}-${file.type}`
if (!map.has(key)) map.set(key, file)
}
return [...map.values()]
}
function handleDrop(event: DragEvent): void {
container.classList.remove('is-dragging')
const files = getDroppedFiles(event)
for (const file of files) {
if (!file.type.startsWith('image/')) continue
attachImage(file)
}
}async function attachImage(file: File): Promise<void> {
const dataUrl = await fileToDataURL(file)
// Extract base64, then store/attach to your UI model
}
function fileToDataURL(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(String(reader.result))
reader.onerror = reject
reader.readAsDataURL(file)
})
}function readUriList(event: DragEvent): string[] {
const raw = event.dataTransfer?.getData('text/uri-list') ?? ''
return raw
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
}This approach enables drag-and-drop from outside VS Code into a webview container without requiring the user to hold SHIFT.