UNPKG

@sv-use/core

Version:

A collection of Svelte 5 utilities.

119 lines (118 loc) 4.33 kB
import { handleEventListener } from '../handle-event-listener/index.svelte.js'; import { noop, normalizeValue, toArray } from '../__internal__/utils.svelte.js'; import { untrack } from 'svelte'; /** * Creates a zone where files can be dropped. * @param target The element that acts as the drop zone. * @param options Additional options to customize the behavior. * @see https://svelte-librarian.github.io/sv-use/docs/core/create-drop-zone */ export function createDropZone(target, options = {}) { const { allowedDataTypes = '*', multiple = true, preventDefaultForUnhandled = false, onDrop = noop, onEnter = noop, onLeave = noop, onOver = noop } = options; let cleanups = []; let counter = 0; let isValid = true; const _target = $derived(normalizeValue(target)); let isOver = $state(false); let files = $state(null); $effect(() => { if (_target) { untrack(() => { cleanups.push(handleEventListener(_target, 'dragenter', (event) => handleDragEvent(event, 'enter')), handleEventListener(_target, 'dragover', (event) => handleDragEvent(event, 'over')), handleEventListener(_target, 'dragleave', (event) => handleDragEvent(event, 'leave')), handleEventListener(_target, 'drop', (event) => handleDragEvent(event, 'drop'))); }); } return () => { cleanup(); cleanups = []; }; }); function getFiles(event) { const list = Array.from(event.dataTransfer?.files ?? []); return list.length === 0 ? null : multiple ? list : [list[0]]; } function checkDataTypes(types) { if (types.length === 0) return false; if (allowedDataTypes instanceof Function && allowedDataTypes.length > 0) { return allowedDataTypes(types); } if (allowedDataTypes === '*') return true; const _allowedTypes = allowedDataTypes; return types.every((type) => { return toArray(normalizeValue(_allowedTypes)).some((allowedType) => { if (allowedType.split('/')[1] === '*') { return type.startsWith(allowedType.split('/')[0]); } return type === allowedType; }); }); } function checkValidity(items) { const types = Array.from(items ?? []).map((item) => item.type); const dataTypesValid = checkDataTypes(types); const multipleFilesValid = multiple || items.length <= 1; return dataTypesValid && multipleFilesValid; } function isSafari() { return /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent) && !('chrome' in window); } function handleDragEvent(event, eventType) { const dataTransferItemList = event.dataTransfer?.items; isValid = (dataTransferItemList && checkValidity(dataTransferItemList)) ?? false; if (preventDefaultForUnhandled) { event.preventDefault(); } if (!isSafari() && !isValid) { if (event.dataTransfer) { event.dataTransfer.dropEffect = 'none'; } return; } event.preventDefault(); if (event.dataTransfer) { event.dataTransfer.dropEffect = 'copy'; } const currentFiles = getFiles(event); switch (eventType) { case 'enter': counter += 1; isOver = true; onEnter(event); break; case 'over': onOver(event); break; case 'leave': counter -= 1; if (counter === 0) { isOver = false; } onLeave(event); break; case 'drop': counter = 0; isOver = false; if (isValid) { files = currentFiles; onDrop(currentFiles, event); } break; } } function cleanup() { cleanups.map((fn) => fn()); } return { get files() { return files; }, set files(v) { files = v; }, get isOver() { return isOver; }, cleanup }; }