Skip to content

Instantly share code, notes, and snippets.

@PriNova
Created January 21, 2026 18:40
Show Gist options
  • Select an option

  • Save PriNova/5dae7cf3cf85b8a0056f4477b28d7a19 to your computer and use it in GitHub Desktop.

Select an option

Save PriNova/5dae7cf3cf85b8a0056f4477b28d7a19 to your computer and use it in GitHub Desktop.
Add image via Drag and Drop into VS Code bypassing limitations

Drag & Drop Images from Outside VS Code (No SHIFT Required)

This pattern bypasses the VS Code webview’s default drag handling by:

  • Capturing drag events at the window level
  • Calling preventDefault() (and often stopImmediatePropagation())
  • Showing a container-specific overlay
  • Reading files from dataTransfer.items / dataTransfer.files

1) Global drag capture (prevents VS Code from intercepting)

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 })

2) Container-level overlay

.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;
}

3) Read dropped files (items + files)

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)
	}
}

4) Convert to inline image payload (example)

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)
	})
}

5) Optional URI-list fallback (when files are not provided)

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('#'))
}

Result

This approach enables drag-and-drop from outside VS Code into a webview container without requiring the user to hold SHIFT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment