@vielzeug/dnd 3.0.0 → 26.10.0
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/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +1 -5
- package/dist/_dev.js.map +1 -1
- package/dist/_shared.cjs +1 -1
- package/dist/_shared.cjs.map +1 -1
- package/dist/_shared.js +1 -22
- package/dist/_shared.js.map +1 -1
- package/dist/_touch.cjs +1 -1
- package/dist/_touch.cjs.map +1 -1
- package/dist/_touch.js +1 -150
- package/dist/_touch.js.map +1 -1
- package/dist/dnd.cjs +1 -1
- package/dist/dnd.cjs.map +1 -1
- package/dist/dnd.iife.js +1 -1
- package/dist/dnd.iife.js.map +1 -1
- package/dist/dnd.js +1 -1
- package/dist/dnd.js.map +1 -1
- package/dist/drop-zone.cjs +1 -1
- package/dist/drop-zone.cjs.map +1 -1
- package/dist/drop-zone.js +1 -115
- package/dist/drop-zone.js.map +1 -1
- package/dist/drop.js +1 -3
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +0 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -15
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/sortable.cjs +1 -1
- package/dist/sortable.cjs.map +1 -1
- package/dist/sortable.d.ts.map +1 -1
- package/dist/sortable.js +1 -417
- package/dist/sortable.js.map +1 -1
- package/package.json +7 -6
package/dist/dnd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dnd.js","names":[],"sources":["../src/_dev.ts","../src/_shared.ts","../src/errors.ts","../src/drop-zone.ts","../src/_touch.ts","../src/sortable.ts"],"sourcesContent":["const isDev = !(globalThis as { __DND_PROD__?: boolean }).__DND_PROD__;\n\n/** @internal @security Messages may include developer-supplied config values. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/dnd] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import type { Disposable } from './types.js';\n\n// ─── Disposable ──────────────────────────────────────────────────────────────\n\n/**\n * Create a `Disposable` backed by an `AbortController`.\n * `dispose()` is idempotent — the signal aborts and `cleanup` runs at most once.\n * Pass the returned `disposalSignal` to `addEventListener` calls so removal happens\n * automatically on dispose, instead of pairing manual `removeEventListener` calls.\n */\nexport function createDisposable(cleanup?: () => void): Disposable {\n let disposed = false;\n const abortController = new AbortController();\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n abortController.abort();\n cleanup?.();\n };\n\n return {\n get disposalSignal() {\n return abortController.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n [Symbol.dispose]: dispose,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function resolveDisabled(disabled: boolean | undefined): boolean {\n return disabled === true;\n}\n","/** Base class for all dnd errors. Use `instanceof DndError` to catch any dnd-originated error. */\nexport class DndError extends Error {\n protected static readonly errorName: string = 'DndError';\n\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = (new.target as typeof DndError).errorName;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a sortable action is called with an invalid scope not created by `createSortableScope()`. */\nexport class DndScopeError extends DndError {\n protected static override readonly errorName = 'DndScopeError';\n\n constructor(message = 'Invalid scope — use createSortableScope() to create scopes.') {\n super(message);\n }\n}\n","import { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Accept matching ─────────────────────────────────────────────────────────\n\n/**\n * Test whether a `File` matches an accept pattern list.\n *\n * Each pattern can be:\n * - A MIME type: `'image/png'`\n * - A MIME wildcard: `'image/*'`\n * - A file extension: `'.pdf'`\n *\n * An empty list accepts everything.\n */\nexport function matchesAccept(file: File, accept: readonly string[]): boolean {\n if (!accept.length) return true;\n\n return accept.some((pattern) => {\n const p = pattern.trim().toLowerCase();\n const type = file.type.toLowerCase();\n\n if (!p) return false;\n if (p.startsWith('.')) return file.name.toLowerCase().endsWith(p);\n if (p.endsWith('/*')) return type.startsWith(p.slice(0, -1));\n\n return type === p;\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DropZoneOptions {\n /**\n * Accepted file types. Each entry may be:\n * - A MIME type: 'image/png'\n * - A MIME wildcard: 'image/*'\n * - A file extension: '.pdf'\n *\n * When empty the zone accepts everything.\n */\n accept?: readonly string[];\n /**\n * When `true`, all drag events are ignored and hover state does not change.\n *\n * Note: a disabled zone does not call `preventDefault` on drag or paste events,\n * so underlying elements (such as text editors) will still receive them.\n */\n disabled?: boolean;\n /**\n * The `dropEffect` to set on `dataTransfer` during `dragover`.\n * @default 'copy'\n */\n dropEffect?: DataTransfer['dropEffect'];\n /** The element to attach drag listeners to. */\n element: HTMLElement;\n /**\n * Maximum number of files accepted per drop. Files beyond this limit are\n * treated as rejected and forwarded to `onDropRejected`.\n *\n * When omitted there is no limit.\n */\n maxFiles?: number;\n /** Called when files are dropped or pasted (when `paste: true` and `onPaste` is omitted). Receives accepted files only. */\n onDrop?: (files: File[]) => void;\n /**\n * Called when dropped or pasted files are rejected by the `accept` filter, `maxFiles` limit, or `onValidate`.\n */\n onDropRejected?: (files: File[]) => void;\n /**\n * Called whenever hover state toggles.\n * Use this for drag-over styling.\n */\n onHoverChange?: (hovered: boolean) => void;\n /**\n * Called when files are pasted via the clipboard. Falls back to `onDrop` when omitted.\n * Only active when `paste: true`.\n */\n onPaste?: (files: File[]) => void;\n /**\n * Optional async file gating. Called after type/extension filtering, before `onDrop`.\n * Return (or resolve) `false` to move all type-accepted files to `onDropRejected`.\n *\n * Only receives type-accepted files (after `accept` and `maxFiles` filtering).\n * Files already rejected by the `accept` filter are forwarded to `onDropRejected`\n * unconditionally and are not passed to this function.\n *\n * While validation is in progress `zone.validating` is `true` and `onValidatingChange`\n * is called with `true`.\n *\n * @example\n * ```ts\n * onValidate: async (files, { signal }) => {\n * const ok = await checkServerQuota(files, { signal });\n * return ok;\n * }\n * ```\n */\n onValidate?: (files: File[], context: DropValidationContext) => boolean | Promise<boolean>;\n /**\n * Called whenever the async validation state changes.\n * Use this to drive loading spinners.\n *\n * @example\n * ```ts\n * onValidatingChange: (v) => { spinnerEl.hidden = !v; }\n * ```\n */\n onValidatingChange?: (validating: boolean) => void;\n /**\n * When `true`, a `paste` event listener is added to the drop-zone element. Pasted files run\n * through the same `accept`, `maxFiles`, and `onValidate` pipeline as dropped files.\n * @default false\n */\n paste?: boolean;\n}\n\nexport interface DropValidationContext {\n /** Aborts when the zone is disposed. Pass this to validation requests. */\n readonly signal: AbortSignal;\n}\n\nexport interface DropZone extends Disposable {\n /** Whether the pointer is currently dragging over the zone. */\n readonly hovered: boolean;\n /** `true` while an `onValidate` promise is pending. */\n readonly validating: boolean;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\n/**\n * DataTransferItem does not expose file names, so extension patterns (for example\n * `.pdf`) cannot be validated during drag-over. We treat these as a permissive\n * pre-check and perform exact filtering in `handleDrop` when `File` objects exist.\n */\nfunction itemsMatchAccept(items: DataTransferItemList, accept: string[]): boolean {\n if (!accept.length) return true;\n\n return Array.from(items).some(\n (item) =>\n item.kind === 'file' &&\n accept.some((pattern) => {\n const p = pattern.trim();\n\n if (p.endsWith('/*')) return item.type.startsWith(p.slice(0, -1));\n\n if (p.startsWith('.')) return true; // extension cannot be checked from DataTransferItem\n\n return item.type === p;\n }),\n );\n}\n\n/**\n * Filter `files` through `accept` patterns and `maxFiles` limit.\n * Returns accepted and rejected arrays.\n */\nfunction applyFileFilters(\n files: File[],\n accept: string[],\n maxFiles: number | undefined,\n): { accepted: File[]; rejected: File[] } {\n const accepted: File[] = [];\n const rejected: File[] = [];\n\n for (const f of files) {\n (matchesAccept(f, accept) ? accepted : rejected).push(f);\n }\n\n if (maxFiles !== undefined && accepted.length > maxFiles) {\n rejected.push(...accepted.splice(maxFiles));\n }\n\n return { accepted, rejected };\n}\n\n// ─── createDropZone ───────────────────────────────────────────────────────────\n\nfunction normalizeAccept(patterns: readonly string[]): string[] {\n return patterns.map((pattern) => {\n const normalized = pattern.trim().toLowerCase();\n const extension = /^\\.[a-z0-9][a-z0-9._+-]*$/.test(normalized);\n const mime = /^[a-z0-9!#$&^_.+-]+\\/(?:\\*|[a-z0-9!#$&^_.+-]+)$/.test(normalized);\n\n if (!extension && !mime) throw new DndError(`Invalid accept pattern: \"${pattern}\"`);\n return normalized;\n });\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n/**\n * Attach drag-and-drop behaviour to a DOM element.\n *\n * Accepts files and provides hover state management with configurable MIME/extension filtering.\n * Calls `onDrop` only for accepted files and `onDropRejected` for filtered files.\n *\n * @example\n * ```ts\n * import { createDropZone } from '@vielzeug/dnd';\n *\n * using zone = createDropZone({\n * element: dropZoneEl,\n * accept: ['image/*', '.pdf'],\n * maxFiles: 5,\n * onDrop: (files) => { console.log('Dropped:', files); },\n * onHoverChange: (hovered) => {\n * dropZoneEl.classList.toggle('drag-over', hovered);\n * },\n * });\n * ```\n */\nexport function createDropZone(options: DropZoneOptions): DropZone {\n const accept = normalizeAccept(options.accept ?? []);\n if (options.maxFiles !== undefined && (!Number.isSafeInteger(options.maxFiles) || options.maxFiles < 0)) {\n throw new DndError('maxFiles must be a non-negative safe integer');\n }\n\n const { dropEffect = 'copy', element, maxFiles, onDrop, onDropRejected, onHoverChange, onValidatingChange } = options;\n\n let dragCounter = 0;\n // Whether the *current* drag's payload passes the accept filter.\n // Determined on the first dragenter and held for the duration of the drag.\n let dragAccepted = false;\n let validating = false;\n const validationControllers = new Set<AbortController>();\n\n const setValidating = (next: boolean): void => {\n if (validating === next) return;\n\n validating = next;\n notify('onValidatingChange', onValidatingChange, next);\n };\n\n const updateCounter = (next: number): void => {\n const wasHovered = dragCounter > 0 && dragAccepted;\n\n dragCounter = Math.max(0, next);\n\n // Reset acceptance state when the drag fully leaves so the next drag starts clean.\n if (dragCounter === 0) dragAccepted = false;\n\n const hovered = dragCounter > 0 && dragAccepted;\n\n if (hovered !== wasHovered) notify('onHoverChange', onHoverChange, hovered);\n };\n\n const resetCounter = (): void => {\n updateCounter(0);\n };\n\n const disposable = createDisposable(() => {\n for (const controller of validationControllers) controller.abort();\n\n validationControllers.clear();\n setValidating(false);\n resetCounter();\n });\n\n // Settle the final accepted/rejected split and fire callbacks.\n const settle = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) notify('onDrop', onDrop, [...acceptedFiles]);\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Settle for paste events (which may use onPaste instead of onDrop).\n const settleForPaste = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) {\n if (options.onPaste) {\n notify('onPaste', options.onPaste, [...acceptedFiles]);\n } else {\n notify('onDrop', onDrop, [...acceptedFiles]);\n }\n }\n\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Run accept/maxFiles filter, then async onValidate, then settle.\n const dispatchWithValidation = (rawFiles: File[], settleFn: (accepted: File[], rejected: File[]) => void): void => {\n const { accepted, rejected: rej } = applyFileFilters(rawFiles, accept, maxFiles);\n const onValidate = options.onValidate;\n const validationController = onValidate && accepted.length > 0 ? new AbortController() : null;\n\n if (validationController) {\n validationControllers.add(validationController);\n setValidating(true);\n }\n\n const finishValidation = (): void => {\n if (!validationController) return;\n\n validationControllers.delete(validationController);\n\n if (!disposable.disposed) setValidating(validationControllers.size > 0);\n };\n\n let validation: boolean | Promise<boolean>;\n\n try {\n validation =\n validationController && onValidate ? onValidate([...accepted], { signal: validationController.signal }) : true;\n } catch (error) {\n validation = Promise.reject(error);\n }\n\n void Promise.resolve(validation).then(\n (valid) => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n if (valid === true) {\n settleFn(accepted, rej);\n } else {\n settleFn([], [...rej, ...accepted]);\n }\n },\n () => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n settleFn([], [...rej, ...accepted]);\n },\n );\n };\n\n const handleDragEnter = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n // Evaluate the filter once per drag (on first entry) — the payload is\n // constant for the lifetime of a drag operation.\n if (dragCounter === 0) {\n const items = e.dataTransfer?.items;\n\n dragAccepted = !accept.length || !items?.length || itemsMatchAccept(items, accept);\n }\n\n if (!dragAccepted && e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n\n // Always increment so every dragenter is paired with its dragleave,\n // regardless of acceptance. This prevents counter under-runs.\n updateCounter(dragCounter + 1);\n };\n\n const handleDragOver = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n if (e.dataTransfer) e.dataTransfer.dropEffect = dragAccepted ? dropEffect : 'none';\n };\n\n const handleDragLeave = (_e: DragEvent): void => {\n // Always decrement to balance the paired dragenter — disabling after enter\n // must not leave the counter permanently incremented.\n updateCounter(dragCounter - 1);\n };\n\n const handleDrop = (e: DragEvent): void => {\n // Reset counter first (idempotent at 0) so hover never sticks even when disabled.\n resetCounter();\n\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n const raw = e.dataTransfer?.files;\n\n if (!raw) return;\n\n dispatchWithValidation(Array.from(raw), settle);\n };\n\n const handlePaste = (e: ClipboardEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n const clipFiles = e.clipboardData?.files;\n\n if (!clipFiles?.length) return;\n\n e.preventDefault();\n dispatchWithValidation(Array.from(clipFiles), settleForPaste);\n };\n\n element.addEventListener('dragenter', handleDragEnter, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('dragleave', handleDragLeave, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n\n if (options.paste) element.addEventListener('paste', handlePaste, { signal: disposable.disposalSignal });\n\n // These global listeners catch drags that end outside the zone.\n // The window 'drop' also fires for in-zone drops, but resetCounter() is idempotent at counter=0.\n window.addEventListener('dragend', resetCounter, { signal: disposable.disposalSignal });\n window.addEventListener('drop', resetCounter, { signal: disposable.disposalSignal });\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get hovered() {\n return dragCounter > 0 && dragAccepted;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n get validating() {\n return validating;\n },\n };\n}\n","import { createDragGesture, type DragGesture, type DragGestureDetail } from '@vielzeug/gesture';\n\nimport { warn } from './_dev.js';\nimport { createDisposable } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\nexport type TouchInputOptions = Readonly<{\n /** Finger movement in pixels required before sorting starts. @default 6 */\n activationDistance?: number;\n /**\n * Renders visual feedback for a touch drag. Return `null` to keep the source item visible\n * instead of rendering a preview. Dnd clones the returned element, preserving caller-owned DOM.\n * The default is an inert outline sized to the source item.\n */\n preview?: false | ((item: HTMLElement) => HTMLElement | null);\n}>;\n\nexport interface ScopeTouchController extends Disposable {\n cancel(): boolean;\n register(element: HTMLElement): () => void;\n}\n\nconst PREVIEW_Z_INDEX = 2147483647;\n\ntype TouchDragSession = {\n current: DragGestureDetail['current'];\n lastTarget: Element | null;\n preview: HTMLElement | null;\n previewOrigin: DragGestureDetail['current'] | null;\n source: HTMLElement;\n};\n\nfunction makeDataTransfer(): DataTransfer {\n return {\n dropEffect: 'move',\n effectAllowed: 'move',\n getData: () => '',\n setData() {},\n setDragImage() {},\n } as unknown as DataTransfer;\n}\n\nfunction createDefaultPreview(source: HTMLElement): HTMLElement {\n const rect = source.getBoundingClientRect();\n const preview = source.ownerDocument.createElement('div');\n preview.setAttribute('aria-hidden', 'true');\n preview.setAttribute('data-dnd-touch-preview', '');\n preview.setAttribute('inert', '');\n Object.assign(preview.style, {\n background: 'transparent',\n border: '2px solid currentColor',\n boxSizing: 'border-box',\n height: `${rect.height}px`,\n left: `${rect.left}px`,\n opacity: '0.7',\n pointerEvents: 'none',\n position: 'fixed',\n top: `${rect.top}px`,\n transition: 'none',\n width: `${rect.width}px`,\n zIndex: String(PREVIEW_Z_INDEX),\n });\n return preview;\n}\n\nexport function createScopeTouchController(\n options: TouchInputOptions,\n resolveDragTarget: (target: Element) => HTMLElement | null,\n): ScopeTouchController {\n const { activationDistance = 6, preview: previewOption } = options;\n if (!Number.isFinite(activationDistance) || activationDistance < 0) {\n throw new DndError('touch.activationDistance must be a non-negative finite number');\n }\n\n const dataTransfer = makeDataTransfer();\n const gestures = new Map<HTMLElement, DragGesture>();\n let pendingSource: HTMLElement | null = null;\n let session: TouchDragSession | null = null;\n\n const resetSession = (): void => {\n session?.preview?.remove();\n pendingSource = null;\n session = null;\n };\n const disposable = createDisposable(() => {\n resetSession();\n gestures.clear();\n });\n\n const eventTarget = (event: PointerEvent): Element | null => {\n const target = event.composedPath().find((node) => (node as Node).nodeType === 1) ?? event.target;\n return target && (target as Node).nodeType === 1 ? (target as Element) : null;\n };\n\n const elementBelow = (active: TouchDragSession, point: DragGestureDetail['current']): Element | null => {\n const previousSourceDisplay = active.source.style.display;\n const previousPreviewDisplay = active.preview?.style.display ?? '';\n active.source.style.display = 'none';\n if (active.preview) active.preview.style.display = 'none';\n try {\n return active.source.ownerDocument.elementFromPoint(point.x, point.y);\n } finally {\n active.source.style.display = previousSourceDisplay;\n if (active.preview) active.preview.style.display = previousPreviewDisplay;\n }\n };\n\n const dispatch = (\n element: Element,\n type: string,\n point: DragGestureDetail['current'],\n hasPreview = false,\n ): boolean => {\n const EventType = element.ownerDocument.defaultView?.Event ?? Event;\n const event = new EventType(type, { bubbles: true, cancelable: true });\n Object.defineProperties(event, {\n __dndTouch: { configurable: true, value: true },\n __dndTouchPreview: { configurable: true, value: hasPreview },\n clientX: { configurable: true, value: point.x },\n clientY: { configurable: true, value: point.y },\n dataTransfer: { configurable: true, value: dataTransfer },\n });\n element.dispatchEvent(event);\n return event.defaultPrevented;\n };\n\n const renderPreview = (source: HTMLElement): HTMLElement | null => {\n if (previewOption === false) return null;\n let previewElement: HTMLElement | null;\n try {\n const preview = previewOption?.(source);\n previewElement = previewOption\n ? ((preview?.cloneNode(true) as HTMLElement | undefined) ?? null)\n : createDefaultPreview(source);\n } catch (error) {\n warn(`touch drag preview failed to render, continuing without one: ${String(error)}`);\n previewElement = null;\n }\n if (!previewElement) return null;\n\n previewElement.setAttribute('aria-hidden', 'true');\n previewElement.setAttribute('data-dnd-touch-preview', '');\n previewElement.setAttribute('inert', '');\n previewElement.removeAttribute('id');\n previewElement.querySelectorAll('[id]').forEach((element) => {\n element.removeAttribute('id');\n });\n previewElement.style.pointerEvents = 'none';\n previewElement.style.position = 'fixed';\n previewElement.style.zIndex = String(PREVIEW_Z_INDEX);\n source.ownerDocument.body.appendChild(previewElement);\n return previewElement;\n };\n\n const start = (detail: DragGestureDetail): void => {\n const source = pendingSource;\n pendingSource = null;\n if (!source) return;\n\n const preview = renderPreview(source);\n const active: TouchDragSession = {\n current: detail.current,\n lastTarget: source,\n preview,\n previewOrigin: preview ? detail.current : null,\n source,\n };\n session = active;\n dispatch(source, 'dragstart', detail.current, preview !== null);\n };\n\n const move = (detail: DragGestureDetail): void => {\n if (!session) return;\n session.current = detail.current;\n if (session.preview && session.previewOrigin) {\n const x = detail.current.x - session.previewOrigin.x;\n const y = detail.current.y - session.previewOrigin.y;\n session.preview.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n }\n\n const below = elementBelow(session, detail.current);\n if (below && below !== session.lastTarget) {\n if (session.lastTarget) dispatch(session.lastTarget, 'dragleave', detail.current);\n session.lastTarget = below;\n }\n if (below) dispatch(below, 'dragover', detail.current);\n detail.event.preventDefault();\n };\n\n const end = (detail: DragGestureDetail & Readonly<{ reason: 'cancel' | 'release' }>): void => {\n pendingSource = null;\n if (!session) return;\n const active = session;\n try {\n let dropAccepted = false;\n if (detail.reason === 'release') {\n const below = elementBelow(active, detail.current);\n if (below) dropAccepted = dispatch(below, 'drop', detail.current);\n }\n dataTransfer.dropEffect = dropAccepted ? 'move' : 'none';\n dispatch(active.source, 'dragend', detail.current, active.preview !== null);\n } finally {\n dataTransfer.dropEffect = 'move';\n resetSession();\n }\n };\n\n return Object.assign(disposable, {\n cancel(): boolean {\n for (const gesture of gestures.values()) {\n if (gesture.cancel()) return true;\n }\n return false;\n },\n register(element: HTMLElement): () => void {\n const gesture = createDragGesture(element, {\n activationDistance,\n onEnd: end,\n onMove: move,\n onStart: start,\n pointerCapture: false,\n shouldStart: (event) => {\n if (event.pointerType !== 'touch' || session) return false;\n const target = eventTarget(event);\n pendingSource = target ? resolveDragTarget(target) : null;\n return pendingSource !== null;\n },\n signal: disposable.disposalSignal,\n });\n gestures.set(element, gesture);\n\n return () => {\n gesture.dispose();\n gestures.delete(element);\n if (pendingSource && element.contains(pendingSource)) pendingSource = null;\n if (session && element.contains(session.source)) resetSession();\n };\n },\n });\n}\n","import { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { createScopeTouchController, type ScopeTouchController, type TouchInputOptions } from './_touch.js';\nimport { DndError, DndScopeError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Branded scope ────────────────────────────────────────────────────────────\n\nconst SCOPE_BRAND = Symbol('SortableScope');\n\nexport interface SortableScope extends Disposable {\n /** `true` while any sortable in this scope is actively dragging. */\n readonly isDragging: boolean;\n readonly [SCOPE_BRAND]: true;\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AutoScrollOptions {\n /** Scroll the sortable container while dragging near its edges. @default true */\n container?: boolean;\n /** Distance in pixels from an edge that triggers auto-scroll. @default 32 */\n edgeThreshold?: number;\n /** Pixels scrolled per dragover frame while near an edge. @default 18 */\n speed?: number;\n /** Scroll the viewport while dragging near the window edges. @default false */\n viewport?: boolean;\n}\n\n/**\n * Passed to `onReorder` after every successful reorder (drag or keyboard).\n * Application history owns rollback — use `before`/`after`/`item` to record\n * an undo entry.\n */\nexport interface ReorderEvent {\n /** Item keys after the reorder. */\n after: readonly string[];\n /** Item keys before the reorder. */\n before: readonly string[];\n /** Stable identity of the moved item. */\n item: string;\n}\n\n/** A single committed move between two containers in the same sortable scope. */\nexport interface SortableMoveEvent {\n /** Stable identity of the moved item. */\n readonly itemId: string;\n /** Source container before the move. */\n readonly source: HTMLElement;\n /** Ordered source item IDs before the move. */\n readonly sourceBeforeIds: readonly string[];\n /** Ordered source item IDs after the move. */\n readonly sourceIds: readonly string[];\n /** Target container after the move. */\n readonly target: HTMLElement;\n /** Ordered target item IDs before the move. */\n readonly targetBeforeIds: readonly string[];\n /** Ordered target item IDs after the move. */\n readonly targetIds: readonly string[];\n}\n\n/**\n * Structured accessibility/observability event for sortable interactions.\n *\n * Drag: `pickup` on dragstart, `drop` on commit, `cancel` on cancel.\n * Keyboard: `move` on each successful arrow/Home/End reorder (direct-commit model).\n *\n * Wire a consumer-side announcer to provide screen-reader feedback:\n * ```ts\n * createSortable({\n * onInteraction(event) {\n * announce(formatSortableEvent(event));\n * },\n * });\n * ```\n */\nexport type SortableInteractionEvent =\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'pickup' }\n | {\n readonly index: number;\n readonly itemId: string;\n readonly previousIndex: number;\n readonly total: number;\n readonly type: 'move';\n }\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'drop' }\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'cancel' };\n\nexport interface SortableScopeOptions {\n /**\n * Called exactly once for every successful cross-container move.\n * Local reorders continue to use each sortable's `onReorder` callback.\n */\n onMove?: (event: SortableMoveEvent) => void;\n /**\n * Enables touch input for sortable items registered to this scope.\n * The controller ignores unrelated document draggables.\n */\n touch?: boolean | TouchInputOptions;\n}\n\nexport type SortableTouchOptions = TouchInputOptions;\n\nexport interface SortableOptions {\n /** Auto-scrolls the container (and viewport) near edges while dragging. @default true */\n autoScroll?: boolean | AutoScrollOptions;\n /** Sorting axis used to compute insertion position. @default 'vertical' */\n axis?: 'vertical' | 'horizontal';\n /**\n * When `true`, drag interactions are ignored.\n *\n * Note: if `disabled` transitions to `true` while a drag is in progress the\n * drag is treated as a cancellation — the item snaps back to its original\n * position rather than committing the last placeholder location.\n */\n disabled?: boolean;\n /** Optional custom drag preview element. */\n dragImage?: HTMLElement | ((id: string, item: HTMLElement, event: DragEvent) => HTMLElement | null | undefined);\n /**\n * Hotspot offset `[x, y]` passed to `setDragImage`.\n * Controls which point of the preview image follows the cursor.\n * @default [0, 0]\n */\n dragImageOffset?: [number, number];\n /** Container element whose direct-child items are sortable. */\n element: HTMLElement;\n /**\n * Returns the identity key for a given item element.\n * This separates the \"what is this item?\" concern (yours) from the \"which children\n * are sortable?\" concern (ours — marked with `data-dnd-item`).\n *\n * @example\n * ```ts\n * getKey: (el) => el.dataset.taskId!\n * ```\n */\n getKey: (element: HTMLElement) => string;\n /**\n * Selector for the drag handle inside each item.\n * When omitted the whole item is the handle.\n */\n handle?: string;\n /**\n * Returns the current sortable item elements. When omitted the sortable\n * scans the container's direct children. Provide this for explicit item\n * ownership — e.g. when items are managed by a framework render loop.\n */\n items?: () => readonly HTMLElement[];\n /**\n * Enables keyboard-based reordering using arrow keys plus Home/End.\n * @default true\n */\n keyboard?: boolean;\n /**\n * Called just before a successful drag commit with the before and after order snapshots.\n * Use this hook to set up FLIP animations — the source items are still in their\n * pre-commit positions at the time of the call.\n *\n * @example\n * ```ts\n * onBeforeReorder: (from, to) => {\n * // record element positions here, then animate after the next microtask\n * }\n * ```\n */\n onBeforeReorder?: (from: readonly string[], to: readonly string[]) => void;\n /** Called when a drag ends (whether dropped or cancelled). */\n onDragEnd?: (id: string, event: DragEvent) => void;\n /** Called when the user starts dragging an item. */\n onDragStart?: (id: string, event: DragEvent) => void;\n /**\n * Structured accessibility event for pickup/move/drop/cancel — wire to a consumer-side\n * announcer for screen-reader feedback. See {@link SortableInteractionEvent}.\n */\n onInteraction?: (event: SortableInteractionEvent) => void;\n /**\n * Called with a {@link ReorderEvent} after a successful reorder, only when the order changed.\n * The event carries `before`, `after`, and `item` so application history can own rollback.\n *\n * @example\n * ```ts\n * onReorder: ({ before, after, item }) => {\n * history.push({ revert: () => setOrder(before) });\n * setOrder(after);\n * },\n * ```\n */\n onReorder?: (event: ReorderEvent) => void;\n /** CSS class applied to the placeholder element. @default 'dnd-placeholder' */\n placeholderClass?: string;\n /** Shared scope for connected sortable containers. Containers only exchange items within the same scope. */\n scope?: SortableScope;\n}\n\nexport interface Sortable extends Disposable {\n readonly isDragging: boolean;\n /**\n * Re-reads items from the `items` provider (or the container's direct children)\n * and reapplies `draggable`, ARIA roles, and handle attributes. Call this after\n * programmatically adding, removing, or replacing items — e.g. after a framework\n * render that replaces DOM nodes.\n *\n * Not needed when items are only reordered via drag or keyboard.\n */\n refresh(): void;\n}\n\n// ─── Internal types ───────────────────────────────────────────────────────────\n\ninterface ResolvedAutoScrollOptions {\n container: boolean;\n edgeThreshold: number;\n speed: number;\n viewport: boolean;\n}\n\n/** Per-container closure passed into shared session functions. */\ninterface ContainerHandle {\n commitReorder: (event: { after: readonly string[]; before: readonly string[]; item: string }) => void;\n readonly element: HTMLElement;\n getOrderedIds: () => string[];\n getOrderedItems: () => HTMLElement[];\n isDisabled: () => boolean;\n notifyBeforeReorder: (from: readonly string[], to: readonly string[]) => void;\n notifyDragEnd: (id: string, event: DragEvent) => void;\n notifyDragStart: (id: string, event: DragEvent) => void;\n notifyInteraction: (event: SortableInteractionEvent) => void;\n resolveTouchTarget: (target: Element) => HTMLElement | null;\n}\n\n/**\n * Encapsulates all mutable state for a single in-progress drag operation.\n * Created at dragstart, destroyed on dragend/cancel.\n */\ninterface DragSession {\n draggedEl: HTMLElement;\n draggedId: string;\n /** rAF handle for the deferred element-hide; null once fired or cancelled. */\n hideFrame: number | null;\n /**\n * Order snapshots captured lazily: snapshot is taken the first time a handle\n * becomes the active target (and always for the source at drag start).\n */\n initialOrders: Map<ContainerHandle, string[]>;\n originalDisplay: string;\n originalNextSibling: ChildNode | null;\n originalParent: HTMLElement;\n placeholder: HTMLElement;\n source: ContainerHandle;\n target: ContainerHandle | null;\n}\n\ninterface SortableScopeState {\n active: DragSession | null;\n commitMove: (event: SortableMoveEvent) => void;\n /** All sortables registered in this scope, kept for scope.dispose(). */\n disposables: Set<() => void>;\n handles: Set<ContainerHandle>;\n touch: ScopeTouchController | null;\n}\n\ninterface ManagedElementState {\n dataDndHandle: string | null;\n dataDndItem: string | null;\n draggable: string | null;\n role: string | null;\n tabIndex: string | null;\n touchAction: string;\n}\n\ninterface TouchDragEvent extends DragEvent {\n readonly __dndTouch?: boolean;\n readonly __dndTouchPreview?: boolean;\n}\n\n// ─── Internal constants ───────────────────────────────────────────────────────\n\nconst HANDLE_ATTR = 'data-dnd-handle';\nconst ITEM_ATTR = 'data-dnd-item';\nconst sortableScopeStates = new WeakMap<SortableScope, SortableScopeState>();\nconst DEFAULT_AUTO_SCROLL: ResolvedAutoScrollOptions = {\n container: true,\n edgeThreshold: 32,\n speed: 18,\n viewport: false,\n};\n\n// ─── Scope helpers ────────────────────────────────────────────────────────────\n\nfunction getSortableScopeState(scope: SortableScope): SortableScopeState {\n const state = sortableScopeStates.get(scope);\n\n if (!state) throw new DndScopeError();\n if (scope.disposed) throw new DndScopeError('Cannot register a sortable with a disposed scope.');\n\n return state;\n}\n\n// ─── Option resolution ────────────────────────────────────────────────────────\n\nfunction resolveAutoScrollOptions(\n autoScroll: boolean | AutoScrollOptions | undefined,\n): ResolvedAutoScrollOptions | null {\n if (autoScroll === false) return null;\n\n if (autoScroll === true || autoScroll === undefined) return DEFAULT_AUTO_SCROLL;\n if (\n !Number.isFinite(autoScroll.edgeThreshold ?? 32) ||\n (autoScroll.edgeThreshold ?? 32) < 0 ||\n !Number.isFinite(autoScroll.speed ?? 18) ||\n (autoScroll.speed ?? 18) <= 0\n ) {\n throw new DndError('Auto-scroll edgeThreshold must be non-negative and speed must be positive');\n }\n\n return {\n container: autoScroll.container ?? true,\n edgeThreshold: autoScroll.edgeThreshold ?? 32,\n speed: autoScroll.speed ?? 18,\n viewport: autoScroll.viewport ?? false,\n };\n}\n\n// ─── DOM helpers ──────────────────────────────────────────────────────────────\n\nfunction hasOrderChanged(before: string[], after: string[]): boolean {\n return after.length !== before.length || after.some((id, index) => id !== before[index]);\n}\n\nfunction isTouchDragEvent(event: DragEvent): event is TouchDragEvent {\n return (event as TouchDragEvent).__dndTouch === true;\n}\n\nfunction immutableIds(ids: readonly string[]): readonly string[] {\n return Object.freeze([...ids]);\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n// ─── Drag session helpers ─────────────────────────────────────────────────────\n\nfunction scheduleHide(session: DragSession): void {\n if (typeof requestAnimationFrame !== 'function') return;\n\n session.hideFrame = requestAnimationFrame(() => {\n session.hideFrame = null;\n session.draggedEl.style.display = 'none';\n });\n}\n\nfunction cancelHide(session: DragSession): void {\n if (session.hideFrame !== null) {\n cancelAnimationFrame(session.hideFrame);\n session.hideFrame = null;\n }\n}\n\nfunction restoreSessionElement(session: DragSession): void {\n cancelHide(session);\n session.draggedEl.style.display = session.originalDisplay;\n session.draggedEl.removeAttribute('data-dragging');\n}\n\n/**\n * Snapshot a handle's current order if not already recorded.\n * Called the first time a handle becomes an active target so we only capture\n * lists that actually participate in the drag.\n */\nfunction snapshotOrder(session: DragSession, handle: ContainerHandle): void {\n if (!session.initialOrders.has(handle)) {\n session.initialOrders.set(handle, handle.getOrderedIds());\n }\n}\n\nfunction projectedChanges(session: DragSession): Array<{ after: string[]; before: string[]; handle: ContainerHandle }> {\n const target = session.target;\n\n return [...session.initialOrders].flatMap(([handle, before]) => {\n const after = before.filter((id) => id !== session.draggedId);\n\n if (handle === target && session.placeholder.parentElement === handle.element) {\n const children = [...handle.element.children];\n const placeholderIndex = children.indexOf(session.placeholder);\n const insertionIndex = handle\n .getOrderedItems()\n .filter((item) => item !== session.draggedEl && children.indexOf(item) < placeholderIndex).length;\n after.splice(insertionIndex, 0, session.draggedId);\n }\n\n return hasOrderChanged(before, after) ? [{ after, before, handle }] : [];\n });\n}\n\n// ─── Session commit / cancel ──────────────────────────────────────────────────\n\nfunction cancelSession(scopeState: SortableScopeState, event: DragEvent): void {\n const session = scopeState.active;\n\n if (!session) return;\n\n restoreSessionElement(session);\n session.originalParent.insertBefore(\n session.draggedEl,\n session.originalNextSibling?.parentNode === session.originalParent ? session.originalNextSibling : null,\n );\n session.placeholder.remove();\n scopeState.active = null;\n\n session.source.notifyDragEnd(session.draggedId, event);\n session.source.notifyInteraction({\n index: session.source.getOrderedIds().indexOf(session.draggedId),\n itemId: session.draggedId,\n total: session.source.getOrderedIds().length,\n type: 'cancel',\n });\n}\n\nfunction commitSession(scopeState: SortableScopeState, event: DragEvent): void {\n const session = scopeState.active;\n\n if (!session) return;\n\n const targetHandle = session.target;\n const targetElement = session.placeholder.parentElement;\n const projected = projectedChanges(session);\n\n for (const { after, before, handle } of projected) {\n handle.notifyBeforeReorder(immutableIds(before), immutableIds(after));\n }\n\n restoreSessionElement(session);\n\n if (targetHandle && scopeState.handles.has(targetHandle) && targetElement) {\n targetElement.insertBefore(session.draggedEl, session.placeholder);\n } else {\n session.originalParent.insertBefore(\n session.draggedEl,\n session.originalNextSibling?.parentNode === session.originalParent ? session.originalNextSibling : null,\n );\n }\n\n session.placeholder.remove();\n scopeState.active = null;\n\n session.source.notifyDragEnd(session.draggedId, event);\n\n const changes: Array<{ after: readonly string[]; before: readonly string[]; handle: ContainerHandle }> = [];\n\n for (const [handle, before] of session.initialOrders) {\n if (!scopeState.handles.has(handle)) continue;\n\n const afterOrder = handle.getOrderedIds();\n\n if (hasOrderChanged(before, afterOrder)) {\n changes.push({ after: afterOrder, before, handle });\n }\n }\n\n if (targetHandle && targetHandle !== session.source) {\n const sourceChange = changes.find((change) => change.handle === session.source);\n const targetChange = changes.find((change) => change.handle === targetHandle);\n\n if (sourceChange && targetChange) {\n scopeState.commitMove({\n itemId: session.draggedId,\n source: session.source.element,\n sourceBeforeIds: sourceChange.before,\n sourceIds: sourceChange.after,\n target: targetHandle.element,\n targetBeforeIds: targetChange.before,\n targetIds: targetChange.after,\n });\n\n const dropIndex = targetChange.after.indexOf(session.draggedId);\n const dropTotal = targetChange.after.length;\n\n // Emit from both source and target so a consumer wiring onInteraction on\n // either container receives the drop event.\n session.source.notifyInteraction({ index: dropIndex, itemId: session.draggedId, total: dropTotal, type: 'drop' });\n targetHandle.notifyInteraction({ index: dropIndex, itemId: session.draggedId, total: dropTotal, type: 'drop' });\n }\n\n return;\n }\n\n for (const { after, before, handle } of changes) {\n handle.commitReorder({ after, before, item: session.draggedId });\n }\n\n // Same-container — emit drop even when the order didn't change (user picked up\n // and dropped at the same position). The interaction completed; silence would\n // leave screen-reader users without confirmation.\n const sourceChange = changes.find((change) => change.handle === session.source);\n const finalOrder = sourceChange ? sourceChange.after : session.source.getOrderedIds();\n const finalIndex = finalOrder.indexOf(session.draggedId);\n\n session.source.notifyInteraction({\n index: finalIndex,\n itemId: session.draggedId,\n total: finalOrder.length,\n type: 'drop',\n });\n}\n\nfunction finishSession(scopeState: SortableScopeState, event: DragEvent, forceCancel: boolean): void {\n if (!scopeState.active) return;\n\n const { active } = scopeState;\n\n // If source or target became disabled during the drag, treat as cancellation.\n if (\n forceCancel ||\n !event.dataTransfer ||\n event.dataTransfer.dropEffect === 'none' ||\n !active.target ||\n !scopeState.handles.has(active.target) ||\n active.source.isDisabled() ||\n active.target.isDisabled()\n ) {\n cancelSession(scopeState, event);\n } else {\n commitSession(scopeState, event);\n }\n}\n\nfunction maybeAutoScroll(\n event: DragEvent,\n container: HTMLElement,\n axis: 'vertical' | 'horizontal',\n options: ResolvedAutoScrollOptions | null,\n): void {\n if (!options) return;\n\n const { container: scrollContainer, edgeThreshold: threshold, speed, viewport } = options;\n const rect = container.getBoundingClientRect();\n\n if (axis === 'vertical') {\n if (scrollContainer && event.clientY < rect.top + threshold) {\n container.scrollTop -= speed;\n } else if (scrollContainer && event.clientY > rect.bottom - threshold) {\n container.scrollTop += speed;\n }\n\n if (viewport && event.clientY < threshold) {\n window.scrollBy({ left: 0, top: -speed });\n } else if (viewport && event.clientY > window.innerHeight - threshold) {\n window.scrollBy({ left: 0, top: speed });\n }\n\n return;\n }\n\n if (scrollContainer && event.clientX < rect.left + threshold) {\n container.scrollLeft -= speed;\n } else if (scrollContainer && event.clientX > rect.right - threshold) {\n container.scrollLeft += speed;\n }\n\n if (viewport && event.clientX < threshold) {\n window.scrollBy({ left: -speed, top: 0 });\n } else if (viewport && event.clientX > window.innerWidth - threshold) {\n window.scrollBy({ left: speed, top: 0 });\n }\n}\n\n// ─── Keyboard reorder ─────────────────────────────────────────────────────────\n\nfunction keyboardTargetIndex(\n items: HTMLElement[],\n item: HTMLElement,\n key: string,\n axis: 'vertical' | 'horizontal',\n): number | null {\n const currentIndex = items.indexOf(item);\n\n if (currentIndex < 0) return null;\n\n const isForward = axis === 'vertical' ? key === 'ArrowDown' : key === 'ArrowRight';\n const isBackward = axis === 'vertical' ? key === 'ArrowUp' : key === 'ArrowLeft';\n let targetIndex: number;\n\n if (isForward) targetIndex = Math.min(items.length - 1, currentIndex + 1);\n else if (isBackward) targetIndex = Math.max(0, currentIndex - 1);\n else if (key === 'Home') targetIndex = 0;\n else if (key === 'End') targetIndex = items.length - 1;\n else return null;\n\n // Already at the boundary — return null so the caller does not call preventDefault\n // and the browser can handle the key (e.g. scrolling the page).\n return targetIndex === currentIndex ? null : targetIndex;\n}\n\n// ─── createSortableScope ──────────────────────────────────────────────────────\n\n/**\n * Create a shared scope for connected sortable containers.\n *\n * Items can be dragged between all sortables that share the same scope.\n *\n * @example\n * ```ts\n * const scope = createSortableScope();\n * const left = createSortable({ element: leftEl, scope, getKey, onReorder });\n * const right = createSortable({ element: rightEl, scope, getKey, onReorder });\n *\n * // Tear down everything at once:\n * scope.dispose();\n * ```\n */\nexport function createSortableScope(options: SortableScopeOptions = {}): SortableScope {\n const state: SortableScopeState = {\n active: null,\n commitMove(event): void {\n notify(\n 'onMove',\n options.onMove,\n Object.freeze({\n ...event,\n sourceBeforeIds: immutableIds(event.sourceBeforeIds),\n sourceIds: immutableIds(event.sourceIds),\n targetBeforeIds: immutableIds(event.targetBeforeIds),\n targetIds: immutableIds(event.targetIds),\n }),\n );\n },\n disposables: new Set(),\n handles: new Set(),\n touch: null,\n };\n const disposable = createDisposable(() => {\n state.touch?.dispose();\n\n // Dispose all registered sortables (each dispose() call is idempotent)\n for (const disposeFn of [...state.disposables]) {\n disposeFn();\n }\n state.disposables.clear();\n state.handles.clear();\n });\n\n const scope = {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get isDragging() {\n return state.active !== null;\n },\n [SCOPE_BRAND]: true as const,\n [Symbol.dispose]: disposable[Symbol.dispose],\n } as SortableScope;\n\n sortableScopeStates.set(scope, state);\n\n if (options.touch) {\n state.touch = createScopeTouchController(options.touch === true ? {} : options.touch, (target) => {\n if (state.active) return null;\n for (const handle of state.handles) {\n const dragTarget = handle.resolveTouchTarget(target);\n\n if (dragTarget) return dragTarget;\n }\n\n return null;\n });\n }\n\n return scope;\n}\n\n// ─── createSortable ───────────────────────────────────────────────────────────\n\n/**\n * Makes a list of items sortable via native HTML drag interactions.\n *\n * Provide `getKey` to map each item element to a stable string identity.\n * `createSortable` marks qualifying children with `data-dnd-item` and sets\n * `draggable=\"true\"` automatically.\n *\n * Style the drop indicator by targeting `.dnd-placeholder` in your CSS.\n * Supports keyboard reordering (arrow keys, Home/End) and auto-scrolling near edges.\n *\n * @example\n * ```ts\n * import { createSortable } from '@vielzeug/dnd/sortable';\n *\n * using sortable = createSortable({\n * element: listEl,\n * getKey: (el) => el.dataset.id!,\n * onReorder: ({ before, after }) => {\n * history.push({ revert: () => setOrder(before) });\n * setOrder(after);\n * },\n * });\n * ```\n */\nexport function createSortable(options: SortableOptions): Sortable {\n const {\n autoScroll = true,\n axis = 'vertical',\n element,\n getKey,\n handle,\n keyboard = true,\n placeholderClass = 'dnd-placeholder',\n scope = createSortableScope(),\n } = options;\n if (axis !== 'vertical' && axis !== 'horizontal') throw new DndError('axis must be \"vertical\" or \"horizontal\"');\n const handleSelector = handle?.trim() || null;\n\n if (handle !== undefined && !handleSelector) {\n warn(\n 'handle option is an empty string — no handle elements will be found. Provide a valid CSS selector or omit the option.',\n );\n } else if (handleSelector) {\n try {\n element.querySelector(handleSelector);\n } catch (error) {\n throw new DndError(`Invalid handle selector: \"${handle}\"`, { cause: error });\n }\n }\n\n const autoScrollOptions = resolveAutoScrollOptions(autoScroll);\n const scopeState = getSortableScopeState(scope);\n if ([...scopeState.handles].some((registered) => registered.element === element)) {\n throw new DndError('A sortable is already registered for this element in the selected scope.');\n }\n const getItems = (): HTMLElement[] =>\n (options.items ? options.items() : (Array.from(element.children) as HTMLElement[])).filter(\n (item) => item.parentElement === element && item.hasAttribute(ITEM_ATTR),\n );\n\n const getOrderedIds = (): string[] => getItems().map((el) => getKey(el));\n const managedElements = new Map<HTMLElement, ManagedElementState>();\n const originalContainerRole = element.getAttribute('role');\n\n const rememberElement = (managedElement: HTMLElement): ManagedElementState => {\n const existing = managedElements.get(managedElement);\n\n if (existing) return existing;\n\n const state: ManagedElementState = {\n dataDndHandle: managedElement.getAttribute(HANDLE_ATTR),\n dataDndItem: managedElement.getAttribute(ITEM_ATTR),\n draggable: managedElement.getAttribute('draggable'),\n role: managedElement.getAttribute('role'),\n tabIndex: managedElement.getAttribute('tabindex'),\n touchAction: managedElement.style.touchAction,\n };\n\n managedElements.set(managedElement, state);\n\n return state;\n };\n\n const restoreAttribute = (managedElement: HTMLElement, name: string, value: string | null): void => {\n if (value === null) {\n managedElement.removeAttribute(name);\n } else {\n managedElement.setAttribute(name, value);\n }\n };\n\n const syncItems = (items = getItems()): void => {\n items.forEach((el) => {\n const itemState = rememberElement(el);\n\n if (itemState.role === null) el.setAttribute('role', 'listitem');\n\n if (itemState.tabIndex === null) el.tabIndex = 0;\n\n if (handle !== undefined) {\n handleSelector &&\n el.querySelectorAll<HTMLElement>(handleSelector).forEach((handleEl) => {\n rememberElement(handleEl);\n handleEl.setAttribute(HANDLE_ATTR, '');\n handleEl.setAttribute('draggable', 'true');\n if (scopeState.touch) handleEl.style.touchAction = 'none';\n });\n } else {\n el.setAttribute('draggable', 'true');\n // A native mouse drag has no competing gesture to arbitrate; touch does. Without this,\n // a mobile browser can decide the very first bit of finger movement is a page\n // scroll/pan — a decision it makes independently of, and before, this library's own\n // Gesture activation-distance/`preventDefault()` logic ever runs — and hand the rest of the\n // gesture to native scrolling. Once that happens the item never receives the\n // `dragover` sequence needed to update the drop target, so the session ends up\n // committing back to wherever it started: indistinguishable from the drop \"reverting\".\n // `touch-action: none` opts the element out of every default touch gesture from\n // `pointerdown` onward, leaving the whole interaction to this library's own JS.\n if (scopeState.touch) el.style.touchAction = 'none';\n }\n });\n };\n\n const markItems = (): void => {\n const seenKeys = new Set<string>();\n\n // When an explicit items provider is given, use it directly; otherwise scan\n // the container's direct children for elements that have a key.\n const provided = options.items ? options.items() : (Array.from(element.children) as HTMLElement[]);\n const candidates = provided.filter((item) => item instanceof HTMLElement && item.parentElement === element);\n const keyed: HTMLElement[] = [];\n\n if (candidates.length !== provided.length) {\n warn('items returned an element that is not a direct child of the sortable container; it was ignored.');\n }\n\n for (const el of candidates) {\n try {\n const key = getKey(el);\n\n if (key) {\n if (seenKeys.has(key)) {\n warn(\n `getKey returned the duplicate key \"${key}\" for two sibling items — the duplicate item was excluded. Ensure getKey returns a unique value per item.`,\n );\n continue;\n }\n\n seenKeys.add(key);\n keyed.push(el);\n }\n } catch (err) {\n warn(\n `getKey threw for a child element — the item will not be sortable. Check your getKey implementation. ${String(err)}`,\n );\n }\n }\n\n cleanupItems();\n for (const el of keyed) {\n rememberElement(el);\n el.setAttribute(ITEM_ATTR, '');\n }\n syncItems(keyed);\n };\n\n const cleanupItems = (): void => {\n for (const [managedElement, state] of managedElements) {\n restoreAttribute(managedElement, HANDLE_ATTR, state.dataDndHandle);\n restoreAttribute(managedElement, ITEM_ATTR, state.dataDndItem);\n restoreAttribute(managedElement, 'draggable', state.draggable);\n restoreAttribute(managedElement, 'role', state.role);\n restoreAttribute(managedElement, 'tabindex', state.tabIndex);\n managedElement.style.touchAction = state.touchAction;\n }\n\n managedElements.clear();\n };\n\n const createPlaceholder = (source: HTMLElement): HTMLElement => {\n const p = document.createElement('div');\n\n p.className = placeholderClass;\n p.setAttribute('aria-hidden', 'true');\n\n if (axis === 'horizontal') {\n p.style.width = `${source.offsetWidth}px`;\n } else {\n p.style.height = `${source.offsetHeight}px`;\n }\n\n return p;\n };\n\n const handle_: ContainerHandle = {\n commitReorder: (event) =>\n notify(\n 'onReorder',\n options.onReorder,\n Object.freeze({ after: immutableIds(event.after), before: immutableIds(event.before), item: event.item }),\n ),\n element,\n getOrderedIds,\n getOrderedItems: getItems,\n isDisabled: () => resolveDisabled(options.disabled),\n notifyBeforeReorder: (from, to) => notify('onBeforeReorder', options.onBeforeReorder, from, to),\n notifyDragEnd: (id, event) => notify('onDragEnd', options.onDragEnd, id, event),\n notifyDragStart: (id, event) => notify('onDragStart', options.onDragStart, id, event),\n notifyInteraction: (event) => notify('onInteraction', options.onInteraction, Object.freeze(event)),\n resolveTouchTarget: (target) => {\n if (resolveDisabled(options.disabled) || !element.contains(target)) return null;\n\n const item = target.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!item || !getItems().includes(item)) return null;\n\n if (handle === undefined) return item;\n if (!handleSelector) return null;\n\n const handleTarget = target.closest<HTMLElement>(handleSelector);\n\n return handleTarget && item.contains(handleTarget) ? handleTarget : null;\n },\n };\n\n const handleDragStart = (e: DragEvent): void => {\n if (scopeState.active) return;\n\n if (handle_.isDisabled()) return;\n\n const target = e.target as HTMLElement;\n const item = target.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!item || !getItems().includes(item)) return;\n\n if (handle !== undefined) {\n const handleTarget = handleSelector ? target.closest(handleSelector) : null;\n if (!handleTarget || !item.contains(handleTarget)) return;\n }\n\n const originalParent = item.parentElement;\n\n if (!originalParent) return;\n\n const placeholder = createPlaceholder(item);\n const originalNextSibling = item.nextSibling;\n const activeId = getKey(item);\n\n // Snapshot only the source handle at drag start; targets are snapshotted lazily.\n const initialOrders = new Map<ContainerHandle, string[]>();\n\n initialOrders.set(handle_, handle_.getOrderedIds());\n item.setAttribute('data-dragging', '');\n originalParent.insertBefore(placeholder, originalNextSibling);\n\n const session: DragSession = {\n draggedEl: item,\n draggedId: activeId,\n hideFrame: null,\n initialOrders,\n originalDisplay: item.style.display,\n originalNextSibling,\n originalParent,\n placeholder,\n source: handle_,\n target: handle_,\n };\n\n if (!isTouchDragEvent(e) || e.__dndTouchPreview) scheduleHide(session);\n\n scopeState.active = session;\n\n if (e.dataTransfer) {\n try {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', activeId);\n\n if (options.dragImage) {\n const preview =\n typeof options.dragImage === 'function' ? options.dragImage(activeId, item, e) : options.dragImage;\n const [offsetX, offsetY] = options.dragImageOffset ?? [0, 0];\n\n if (preview) e.dataTransfer.setDragImage(preview, offsetX, offsetY);\n }\n } catch (error) {\n warn(`drag preview setup failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n handle_.notifyDragStart(session.draggedId, e);\n handle_.notifyInteraction({\n index: handle_.getOrderedIds().indexOf(activeId),\n itemId: activeId,\n total: handle_.getOrderedIds().length,\n type: 'pickup',\n });\n };\n\n const handleDragOver = (e: DragEvent): void => {\n const session = scopeState.active;\n\n if (!session) return;\n\n if (session.source.isDisabled() || handle_.isDisabled()) return;\n\n e.preventDefault();\n maybeAutoScroll(e, element, axis, autoScrollOptions);\n\n // Lazily snapshot this handle's order the first time it becomes a target.\n snapshotOrder(session, handle_);\n\n const { draggedEl, placeholder } = session;\n const target = (e.target as HTMLElement).closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (target && !getItems().includes(target)) return;\n\n if (!target) {\n // Only append placeholder when it isn't already inside this container.\n // Moving it to the end on every over-empty-space event causes the\n // placeholder to oscillate between positions as the cursor moves.\n if (placeholder.parentElement !== element) {\n element.appendChild(placeholder);\n }\n\n session.target = handle_;\n\n return;\n }\n\n if (target === draggedEl || target === placeholder) return;\n\n const rect = target.getBoundingClientRect();\n const insertAfter =\n axis === 'vertical' ? e.clientY >= rect.top + rect.height / 2 : e.clientX >= rect.left + rect.width / 2;\n\n element.insertBefore(placeholder, insertAfter ? target.nextSibling : target);\n session.target = handle_;\n };\n\n const handleDrop = (e: DragEvent): void => {\n const session = scopeState.active;\n\n if (!session) return;\n\n if (session.source.isDisabled() || handle_.isDisabled()) return;\n\n e.preventDefault();\n // Record the drop target; the actual commit happens in handleDragEnd where\n // dataTransfer.dropEffect tells us whether the browser accepted the operation.\n session.target = handle_;\n };\n\n const handleDragEnd = (e: DragEvent): void => {\n if (scopeState.active?.source !== handle_) return;\n\n finishSession(scopeState, e, false);\n };\n\n const handleKeydown = (e: KeyboardEvent): void => {\n if (!keyboard || handle_.isDisabled()) return;\n\n const eventTarget = e.target as HTMLElement | null;\n const item = eventTarget?.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!eventTarget || !item || !getItems().includes(item)) return;\n\n const interactive = eventTarget.closest<HTMLElement>(\n 'input, textarea, select, button, a[href], [contenteditable]:not([contenteditable=\"false\"]), [role=\"button\"], [role=\"link\"]',\n );\n const keyboardHandle = handleSelector ? eventTarget.closest<HTMLElement>(handleSelector) : null;\n\n if (interactive && (!keyboardHandle || !item.contains(keyboardHandle))) return;\n\n const items = getItems();\n const prevIndex = items.indexOf(item);\n const targetIndex = keyboardTargetIndex(items, item, e.key, axis);\n\n // null means unrecognized key or boundary — let the browser handle it (e.g. page scroll)\n if (targetIndex === null) return;\n\n const targetItem = items[targetIndex];\n if (!targetItem) return;\n\n const prevOrder = items.map(getKey);\n const reorderedItems = [...items];\n reorderedItems.splice(prevIndex, 1);\n reorderedItems.splice(targetIndex, 0, item);\n const newOrder = reorderedItems.map(getKey);\n const activeId = getKey(item);\n\n e.preventDefault();\n handle_.notifyBeforeReorder(immutableIds(prevOrder), immutableIds(newOrder));\n element.insertBefore(item, targetIndex > prevIndex ? targetItem.nextSibling : targetItem);\n item.focus();\n handle_.commitReorder({ after: newOrder, before: prevOrder, item: activeId });\n handle_.notifyInteraction({\n index: targetIndex,\n itemId: activeId,\n previousIndex: prevIndex,\n total: newOrder.length,\n type: 'move',\n });\n };\n\n markItems();\n\n let stopTouch: (() => void) | undefined;\n const disposable = createDisposable(() => {\n scopeState.disposables.delete(disposable.dispose);\n\n if (\n scopeState.active &&\n (scopeState.active.source === handle_ || scopeState.active.target === handle_) &&\n !scopeState.touch?.cancel()\n ) {\n finishSession(scopeState, new Event('dragend') as DragEvent, true);\n }\n\n stopTouch?.();\n scopeState.handles.delete(handle_);\n restoreAttribute(element, 'role', originalContainerRole);\n cleanupItems();\n });\n\n scopeState.handles.add(handle_);\n stopTouch = scopeState.touch?.register(element);\n\n if (originalContainerRole === null) element.setAttribute('role', 'list');\n\n element.addEventListener('dragstart', handleDragStart, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n element.addEventListener('dragend', handleDragEnd, { signal: disposable.disposalSignal });\n element.addEventListener('keydown', handleKeydown, { signal: disposable.disposalSignal });\n\n // Register with scope so scope.dispose() can tear this down\n scopeState.disposables.add(disposable.dispose);\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get isDragging() {\n return scopeState.active?.source === handle_;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n refresh: () => {\n markItems();\n },\n };\n}\n\n// ─── applyReorder ─────────────────────────────────────────────────────────────\n\n/**\n * Applies a sorted key array to a backing data array.\n * Unknown keys are ignored; omitted items are appended and duplicate backing keys throw `DndError`.\n */\nexport function applyReorder<T>(items: readonly T[], ids: readonly string[], getKey: (item: T) => string): T[] {\n const byId = new Map<string, T>();\n\n for (const item of items) {\n const id = getKey(item);\n if (byId.has(id)) throw new DndError(`Duplicate item key: \"${id}\"`);\n byId.set(id, item);\n }\n const ordered: T[] = [];\n\n for (const id of ids) {\n if (!byId.has(id)) continue;\n\n const item = byId.get(id) as T;\n\n ordered.push(item);\n byId.delete(id);\n }\n\n for (const item of byId.values()) ordered.push(item);\n\n return ordered;\n}\n\nexport { DndError, DndScopeError } from './errors.js';\nexport type { Disposable } from './types.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dnd.js","names":[],"sources":["../src/_dev.ts","../src/_shared.ts","../src/errors.ts","../src/drop-zone.ts","../src/_touch.ts","../src/sortable.ts"],"sourcesContent":["const isDev = !(globalThis as { __DND_PROD__?: boolean }).__DND_PROD__;\n\n/** @internal @security Messages may include developer-supplied config values. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/dnd] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import type { Disposable } from './types.js';\n\n// ─── Disposable ──────────────────────────────────────────────────────────────\n\n/**\n * Create a `Disposable` backed by an `AbortController`.\n * `dispose()` is idempotent — the signal aborts and `cleanup` runs at most once.\n * Pass the returned `disposalSignal` to `addEventListener` calls so removal happens\n * automatically on dispose, instead of pairing manual `removeEventListener` calls.\n */\nexport function createDisposable(cleanup?: () => void): Disposable {\n let disposed = false;\n const abortController = new AbortController();\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n abortController.abort();\n cleanup?.();\n };\n\n return {\n get disposalSignal() {\n return abortController.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n [Symbol.dispose]: dispose,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function resolveDisabled(disabled: boolean | undefined): boolean {\n return disabled === true;\n}\n","/** Base class for all dnd errors. Use `instanceof DndError` to catch any dnd-originated error. */\nexport class DndError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a sortable action is called with an invalid scope not created by `createSortableScope()`. */\nexport class DndScopeError extends DndError {\n constructor(message = 'Invalid scope — use createSortableScope() to create scopes.') {\n super(message);\n }\n}\n","import { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Accept matching ─────────────────────────────────────────────────────────\n\n/**\n * Test whether a `File` matches an accept pattern list.\n *\n * Each pattern can be:\n * - A MIME type: `'image/png'`\n * - A MIME wildcard: `'image/*'`\n * - A file extension: `'.pdf'`\n *\n * An empty list accepts everything.\n */\nexport function matchesAccept(file: File, accept: readonly string[]): boolean {\n if (!accept.length) return true;\n\n return accept.some((pattern) => {\n const p = pattern.trim().toLowerCase();\n const type = file.type.toLowerCase();\n\n if (!p) return false;\n if (p.startsWith('.')) return file.name.toLowerCase().endsWith(p);\n if (p.endsWith('/*')) return type.startsWith(p.slice(0, -1));\n\n return type === p;\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DropZoneOptions {\n /**\n * Accepted file types. Each entry may be:\n * - A MIME type: 'image/png'\n * - A MIME wildcard: 'image/*'\n * - A file extension: '.pdf'\n *\n * When empty the zone accepts everything.\n */\n accept?: readonly string[];\n /**\n * When `true`, all drag events are ignored and hover state does not change.\n *\n * Note: a disabled zone does not call `preventDefault` on drag or paste events,\n * so underlying elements (such as text editors) will still receive them.\n */\n disabled?: boolean;\n /**\n * The `dropEffect` to set on `dataTransfer` during `dragover`.\n * @default 'copy'\n */\n dropEffect?: DataTransfer['dropEffect'];\n /** The element to attach drag listeners to. */\n element: HTMLElement;\n /**\n * Maximum number of files accepted per drop. Files beyond this limit are\n * treated as rejected and forwarded to `onDropRejected`.\n *\n * When omitted there is no limit.\n */\n maxFiles?: number;\n /** Called when files are dropped or pasted (when `paste: true` and `onPaste` is omitted). Receives accepted files only. */\n onDrop?: (files: File[]) => void;\n /**\n * Called when dropped or pasted files are rejected by the `accept` filter, `maxFiles` limit, or `onValidate`.\n */\n onDropRejected?: (files: File[]) => void;\n /**\n * Called whenever hover state toggles.\n * Use this for drag-over styling.\n */\n onHoverChange?: (hovered: boolean) => void;\n /**\n * Called when files are pasted via the clipboard. Falls back to `onDrop` when omitted.\n * Only active when `paste: true`.\n */\n onPaste?: (files: File[]) => void;\n /**\n * Optional async file gating. Called after type/extension filtering, before `onDrop`.\n * Return (or resolve) `false` to move all type-accepted files to `onDropRejected`.\n *\n * Only receives type-accepted files (after `accept` and `maxFiles` filtering).\n * Files already rejected by the `accept` filter are forwarded to `onDropRejected`\n * unconditionally and are not passed to this function.\n *\n * While validation is in progress `zone.validating` is `true` and `onValidatingChange`\n * is called with `true`.\n *\n * @example\n * ```ts\n * onValidate: async (files, { signal }) => {\n * const ok = await checkServerQuota(files, { signal });\n * return ok;\n * }\n * ```\n */\n onValidate?: (files: File[], context: DropValidationContext) => boolean | Promise<boolean>;\n /**\n * Called whenever the async validation state changes.\n * Use this to drive loading spinners.\n *\n * @example\n * ```ts\n * onValidatingChange: (v) => { spinnerEl.hidden = !v; }\n * ```\n */\n onValidatingChange?: (validating: boolean) => void;\n /**\n * When `true`, a `paste` event listener is added to the drop-zone element. Pasted files run\n * through the same `accept`, `maxFiles`, and `onValidate` pipeline as dropped files.\n * @default false\n */\n paste?: boolean;\n}\n\nexport interface DropValidationContext {\n /** Aborts when the zone is disposed. Pass this to validation requests. */\n readonly signal: AbortSignal;\n}\n\nexport interface DropZone extends Disposable {\n /** Whether the pointer is currently dragging over the zone. */\n readonly hovered: boolean;\n /** `true` while an `onValidate` promise is pending. */\n readonly validating: boolean;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\n/**\n * DataTransferItem does not expose file names, so extension patterns (for example\n * `.pdf`) cannot be validated during drag-over. We treat these as a permissive\n * pre-check and perform exact filtering in `handleDrop` when `File` objects exist.\n */\nfunction itemsMatchAccept(items: DataTransferItemList, accept: string[]): boolean {\n if (!accept.length) return true;\n\n return Array.from(items).some(\n (item) =>\n item.kind === 'file' &&\n accept.some((pattern) => {\n const p = pattern.trim();\n\n if (p.endsWith('/*')) return item.type.startsWith(p.slice(0, -1));\n\n if (p.startsWith('.')) return true; // extension cannot be checked from DataTransferItem\n\n return item.type === p;\n }),\n );\n}\n\n/**\n * Filter `files` through `accept` patterns and `maxFiles` limit.\n * Returns accepted and rejected arrays.\n */\nfunction applyFileFilters(\n files: File[],\n accept: string[],\n maxFiles: number | undefined,\n): { accepted: File[]; rejected: File[] } {\n const accepted: File[] = [];\n const rejected: File[] = [];\n\n for (const f of files) {\n (matchesAccept(f, accept) ? accepted : rejected).push(f);\n }\n\n if (maxFiles !== undefined && accepted.length > maxFiles) {\n rejected.push(...accepted.splice(maxFiles));\n }\n\n return { accepted, rejected };\n}\n\n// ─── createDropZone ───────────────────────────────────────────────────────────\n\nfunction normalizeAccept(patterns: readonly string[]): string[] {\n return patterns.map((pattern) => {\n const normalized = pattern.trim().toLowerCase();\n const extension = /^\\.[a-z0-9][a-z0-9._+-]*$/.test(normalized);\n const mime = /^[a-z0-9!#$&^_.+-]+\\/(?:\\*|[a-z0-9!#$&^_.+-]+)$/.test(normalized);\n\n if (!extension && !mime) throw new DndError(`Invalid accept pattern: \"${pattern}\"`);\n return normalized;\n });\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n/**\n * Attach drag-and-drop behaviour to a DOM element.\n *\n * Accepts files and provides hover state management with configurable MIME/extension filtering.\n * Calls `onDrop` only for accepted files and `onDropRejected` for filtered files.\n *\n * @example\n * ```ts\n * import { createDropZone } from '@vielzeug/dnd';\n *\n * using zone = createDropZone({\n * element: dropZoneEl,\n * accept: ['image/*', '.pdf'],\n * maxFiles: 5,\n * onDrop: (files) => { console.log('Dropped:', files); },\n * onHoverChange: (hovered) => {\n * dropZoneEl.classList.toggle('drag-over', hovered);\n * },\n * });\n * ```\n */\nexport function createDropZone(options: DropZoneOptions): DropZone {\n const accept = normalizeAccept(options.accept ?? []);\n if (options.maxFiles !== undefined && (!Number.isSafeInteger(options.maxFiles) || options.maxFiles < 0)) {\n throw new DndError('maxFiles must be a non-negative safe integer');\n }\n\n const { dropEffect = 'copy', element, maxFiles, onDrop, onDropRejected, onHoverChange, onValidatingChange } = options;\n\n let dragCounter = 0;\n // Whether the *current* drag's payload passes the accept filter.\n // Determined on the first dragenter and held for the duration of the drag.\n let dragAccepted = false;\n let validating = false;\n const validationControllers = new Set<AbortController>();\n\n const setValidating = (next: boolean): void => {\n if (validating === next) return;\n\n validating = next;\n notify('onValidatingChange', onValidatingChange, next);\n };\n\n const updateCounter = (next: number): void => {\n const wasHovered = dragCounter > 0 && dragAccepted;\n\n dragCounter = Math.max(0, next);\n\n // Reset acceptance state when the drag fully leaves so the next drag starts clean.\n if (dragCounter === 0) dragAccepted = false;\n\n const hovered = dragCounter > 0 && dragAccepted;\n\n if (hovered !== wasHovered) notify('onHoverChange', onHoverChange, hovered);\n };\n\n const resetCounter = (): void => {\n updateCounter(0);\n };\n\n const disposable = createDisposable(() => {\n for (const controller of validationControllers) controller.abort();\n\n validationControllers.clear();\n setValidating(false);\n resetCounter();\n });\n\n // Settle the final accepted/rejected split and fire callbacks.\n const settle = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) notify('onDrop', onDrop, [...acceptedFiles]);\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Settle for paste events (which may use onPaste instead of onDrop).\n const settleForPaste = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) {\n if (options.onPaste) {\n notify('onPaste', options.onPaste, [...acceptedFiles]);\n } else {\n notify('onDrop', onDrop, [...acceptedFiles]);\n }\n }\n\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Run accept/maxFiles filter, then async onValidate, then settle.\n const dispatchWithValidation = (rawFiles: File[], settleFn: (accepted: File[], rejected: File[]) => void): void => {\n const { accepted, rejected: rej } = applyFileFilters(rawFiles, accept, maxFiles);\n const onValidate = options.onValidate;\n const validationController = onValidate && accepted.length > 0 ? new AbortController() : null;\n\n if (validationController) {\n validationControllers.add(validationController);\n setValidating(true);\n }\n\n const finishValidation = (): void => {\n if (!validationController) return;\n\n validationControllers.delete(validationController);\n\n if (!disposable.disposed) setValidating(validationControllers.size > 0);\n };\n\n let validation: boolean | Promise<boolean>;\n\n try {\n validation =\n validationController && onValidate ? onValidate([...accepted], { signal: validationController.signal }) : true;\n } catch (error) {\n validation = Promise.reject(error);\n }\n\n void Promise.resolve(validation).then(\n (valid) => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n if (valid === true) {\n settleFn(accepted, rej);\n } else {\n settleFn([], [...rej, ...accepted]);\n }\n },\n () => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n settleFn([], [...rej, ...accepted]);\n },\n );\n };\n\n const handleDragEnter = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n // Evaluate the filter once per drag (on first entry) — the payload is\n // constant for the lifetime of a drag operation.\n if (dragCounter === 0) {\n const items = e.dataTransfer?.items;\n\n dragAccepted = !accept.length || !items?.length || itemsMatchAccept(items, accept);\n }\n\n if (!dragAccepted && e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n\n // Always increment so every dragenter is paired with its dragleave,\n // regardless of acceptance. This prevents counter under-runs.\n updateCounter(dragCounter + 1);\n };\n\n const handleDragOver = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n if (e.dataTransfer) e.dataTransfer.dropEffect = dragAccepted ? dropEffect : 'none';\n };\n\n const handleDragLeave = (_e: DragEvent): void => {\n // Always decrement to balance the paired dragenter — disabling after enter\n // must not leave the counter permanently incremented.\n updateCounter(dragCounter - 1);\n };\n\n const handleDrop = (e: DragEvent): void => {\n // Reset counter first (idempotent at 0) so hover never sticks even when disabled.\n resetCounter();\n\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n const raw = e.dataTransfer?.files;\n\n if (!raw) return;\n\n dispatchWithValidation(Array.from(raw), settle);\n };\n\n const handlePaste = (e: ClipboardEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n const clipFiles = e.clipboardData?.files;\n\n if (!clipFiles?.length) return;\n\n e.preventDefault();\n dispatchWithValidation(Array.from(clipFiles), settleForPaste);\n };\n\n element.addEventListener('dragenter', handleDragEnter, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('dragleave', handleDragLeave, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n\n if (options.paste) element.addEventListener('paste', handlePaste, { signal: disposable.disposalSignal });\n\n // These global listeners catch drags that end outside the zone.\n // The window 'drop' also fires for in-zone drops, but resetCounter() is idempotent at counter=0.\n window.addEventListener('dragend', resetCounter, { signal: disposable.disposalSignal });\n window.addEventListener('drop', resetCounter, { signal: disposable.disposalSignal });\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get hovered() {\n return dragCounter > 0 && dragAccepted;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n get validating() {\n return validating;\n },\n };\n}\n","import { createDragGesture, type DragGesture, type DragGestureDetail } from '@vielzeug/gesture';\n\nimport { warn } from './_dev.js';\nimport { createDisposable } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\nexport type TouchInputOptions = Readonly<{\n /** Finger movement in pixels required before sorting starts. @default 6 */\n activationDistance?: number;\n /**\n * Renders visual feedback for a touch drag. Return `null` to keep the source item visible\n * instead of rendering a preview. Dnd clones the returned element, preserving caller-owned DOM.\n * The default is an inert outline sized to the source item.\n */\n preview?: false | ((item: HTMLElement) => HTMLElement | null);\n}>;\n\nexport interface ScopeTouchController extends Disposable {\n cancel(): boolean;\n register(element: HTMLElement): () => void;\n}\n\nconst PREVIEW_Z_INDEX = 2147483647;\n\ntype TouchDragSession = {\n current: DragGestureDetail['current'];\n lastTarget: Element | null;\n preview: HTMLElement | null;\n previewOrigin: DragGestureDetail['current'] | null;\n source: HTMLElement;\n};\n\nfunction makeDataTransfer(): DataTransfer {\n return {\n dropEffect: 'move',\n effectAllowed: 'move',\n getData: () => '',\n setData() {},\n setDragImage() {},\n } as unknown as DataTransfer;\n}\n\nfunction createDefaultPreview(source: HTMLElement): HTMLElement {\n const rect = source.getBoundingClientRect();\n const preview = source.ownerDocument.createElement('div');\n preview.setAttribute('aria-hidden', 'true');\n preview.setAttribute('data-dnd-touch-preview', '');\n preview.setAttribute('inert', '');\n Object.assign(preview.style, {\n background: 'transparent',\n border: '2px solid currentColor',\n boxSizing: 'border-box',\n height: `${rect.height}px`,\n left: `${rect.left}px`,\n opacity: '0.7',\n pointerEvents: 'none',\n position: 'fixed',\n top: `${rect.top}px`,\n transition: 'none',\n width: `${rect.width}px`,\n zIndex: String(PREVIEW_Z_INDEX),\n });\n return preview;\n}\n\nexport function createScopeTouchController(\n options: TouchInputOptions,\n resolveDragTarget: (target: Element) => HTMLElement | null,\n): ScopeTouchController {\n const { activationDistance = 6, preview: previewOption } = options;\n if (!Number.isFinite(activationDistance) || activationDistance < 0) {\n throw new DndError('touch.activationDistance must be a non-negative finite number');\n }\n\n const dataTransfer = makeDataTransfer();\n const gestures = new Map<HTMLElement, DragGesture>();\n let pendingSource: HTMLElement | null = null;\n let session: TouchDragSession | null = null;\n\n const resetSession = (): void => {\n session?.preview?.remove();\n pendingSource = null;\n session = null;\n };\n const disposable = createDisposable(() => {\n resetSession();\n gestures.clear();\n });\n\n const eventTarget = (event: PointerEvent): Element | null => {\n const target = event.composedPath().find((node) => (node as Node).nodeType === 1) ?? event.target;\n return target && (target as Node).nodeType === 1 ? (target as Element) : null;\n };\n\n const elementBelow = (active: TouchDragSession, point: DragGestureDetail['current']): Element | null => {\n const previousSourceDisplay = active.source.style.display;\n const previousPreviewDisplay = active.preview?.style.display ?? '';\n active.source.style.display = 'none';\n if (active.preview) active.preview.style.display = 'none';\n try {\n return active.source.ownerDocument.elementFromPoint(point.x, point.y);\n } finally {\n active.source.style.display = previousSourceDisplay;\n if (active.preview) active.preview.style.display = previousPreviewDisplay;\n }\n };\n\n const dispatch = (\n element: Element,\n type: string,\n point: DragGestureDetail['current'],\n hasPreview = false,\n ): boolean => {\n const EventType = element.ownerDocument.defaultView?.Event ?? Event;\n const event = new EventType(type, { bubbles: true, cancelable: true });\n Object.defineProperties(event, {\n __dndTouch: { configurable: true, value: true },\n __dndTouchPreview: { configurable: true, value: hasPreview },\n clientX: { configurable: true, value: point.x },\n clientY: { configurable: true, value: point.y },\n dataTransfer: { configurable: true, value: dataTransfer },\n });\n element.dispatchEvent(event);\n return event.defaultPrevented;\n };\n\n const renderPreview = (source: HTMLElement): HTMLElement | null => {\n if (previewOption === false) return null;\n let previewElement: HTMLElement | null;\n try {\n const preview = previewOption?.(source);\n previewElement = previewOption\n ? ((preview?.cloneNode(true) as HTMLElement | undefined) ?? null)\n : createDefaultPreview(source);\n } catch (error) {\n warn(`touch drag preview failed to render, continuing without one: ${String(error)}`);\n previewElement = null;\n }\n if (!previewElement) return null;\n\n previewElement.setAttribute('aria-hidden', 'true');\n previewElement.setAttribute('data-dnd-touch-preview', '');\n previewElement.setAttribute('inert', '');\n previewElement.removeAttribute('id');\n previewElement.querySelectorAll('[id]').forEach((element) => {\n element.removeAttribute('id');\n });\n previewElement.style.pointerEvents = 'none';\n previewElement.style.position = 'fixed';\n previewElement.style.zIndex = String(PREVIEW_Z_INDEX);\n source.ownerDocument.body.appendChild(previewElement);\n return previewElement;\n };\n\n const start = (detail: DragGestureDetail): void => {\n const source = pendingSource;\n pendingSource = null;\n if (!source) return;\n\n const preview = renderPreview(source);\n const active: TouchDragSession = {\n current: detail.current,\n lastTarget: source,\n preview,\n previewOrigin: preview ? detail.current : null,\n source,\n };\n session = active;\n dispatch(source, 'dragstart', detail.current, preview !== null);\n };\n\n const move = (detail: DragGestureDetail): void => {\n if (!session) return;\n session.current = detail.current;\n if (session.preview && session.previewOrigin) {\n const x = detail.current.x - session.previewOrigin.x;\n const y = detail.current.y - session.previewOrigin.y;\n session.preview.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n }\n\n const below = elementBelow(session, detail.current);\n if (below && below !== session.lastTarget) {\n if (session.lastTarget) dispatch(session.lastTarget, 'dragleave', detail.current);\n session.lastTarget = below;\n }\n if (below) dispatch(below, 'dragover', detail.current);\n detail.event.preventDefault();\n };\n\n const end = (detail: DragGestureDetail & Readonly<{ reason: 'cancel' | 'release' }>): void => {\n pendingSource = null;\n if (!session) return;\n const active = session;\n try {\n let dropAccepted = false;\n if (detail.reason === 'release') {\n const below = elementBelow(active, detail.current);\n if (below) dropAccepted = dispatch(below, 'drop', detail.current);\n }\n dataTransfer.dropEffect = dropAccepted ? 'move' : 'none';\n dispatch(active.source, 'dragend', detail.current, active.preview !== null);\n } finally {\n dataTransfer.dropEffect = 'move';\n resetSession();\n }\n };\n\n return Object.assign(disposable, {\n cancel(): boolean {\n for (const gesture of gestures.values()) {\n if (gesture.cancel()) return true;\n }\n return false;\n },\n register(element: HTMLElement): () => void {\n const gesture = createDragGesture(element, {\n activationDistance,\n onEnd: end,\n onMove: move,\n onStart: start,\n pointerCapture: false,\n shouldStart: (event) => {\n if (event.pointerType !== 'touch' || session) return false;\n const target = eventTarget(event);\n pendingSource = target ? resolveDragTarget(target) : null;\n return pendingSource !== null;\n },\n signal: disposable.disposalSignal,\n });\n gestures.set(element, gesture);\n\n return () => {\n gesture.dispose();\n gestures.delete(element);\n if (pendingSource && element.contains(pendingSource)) pendingSource = null;\n if (session && element.contains(session.source)) resetSession();\n };\n },\n });\n}\n","import { matchKey } from '@vielzeug/keymap';\n\nimport { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { createScopeTouchController, type ScopeTouchController, type TouchInputOptions } from './_touch.js';\nimport { DndError, DndScopeError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Branded scope ────────────────────────────────────────────────────────────\n\nconst SCOPE_BRAND = Symbol('SortableScope');\n\nexport interface SortableScope extends Disposable {\n /** `true` while any sortable in this scope is actively dragging. */\n readonly isDragging: boolean;\n readonly [SCOPE_BRAND]: true;\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AutoScrollOptions {\n /** Scroll the sortable container while dragging near its edges. @default true */\n container?: boolean;\n /** Distance in pixels from an edge that triggers auto-scroll. @default 32 */\n edgeThreshold?: number;\n /** Pixels scrolled per dragover frame while near an edge. @default 18 */\n speed?: number;\n /** Scroll the viewport while dragging near the window edges. @default false */\n viewport?: boolean;\n}\n\n/**\n * Passed to `onReorder` after every successful reorder (drag or keyboard).\n * Application history owns rollback — use `before`/`after`/`item` to record\n * an undo entry.\n */\nexport interface ReorderEvent {\n /** Item keys after the reorder. */\n after: readonly string[];\n /** Item keys before the reorder. */\n before: readonly string[];\n /** Stable identity of the moved item. */\n item: string;\n}\n\n/** A single committed move between two containers in the same sortable scope. */\nexport interface SortableMoveEvent {\n /** Stable identity of the moved item. */\n readonly itemId: string;\n /** Source container before the move. */\n readonly source: HTMLElement;\n /** Ordered source item IDs before the move. */\n readonly sourceBeforeIds: readonly string[];\n /** Ordered source item IDs after the move. */\n readonly sourceIds: readonly string[];\n /** Target container after the move. */\n readonly target: HTMLElement;\n /** Ordered target item IDs before the move. */\n readonly targetBeforeIds: readonly string[];\n /** Ordered target item IDs after the move. */\n readonly targetIds: readonly string[];\n}\n\n/**\n * Structured accessibility/observability event for sortable interactions.\n *\n * Drag: `pickup` on dragstart, `drop` on commit, `cancel` on cancel.\n * Keyboard: `move` on each successful arrow/Home/End reorder (direct-commit model).\n *\n * Wire a consumer-side announcer to provide screen-reader feedback:\n * ```ts\n * createSortable({\n * onInteraction(event) {\n * announce(formatSortableEvent(event));\n * },\n * });\n * ```\n */\nexport type SortableInteractionEvent =\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'pickup' }\n | {\n readonly index: number;\n readonly itemId: string;\n readonly previousIndex: number;\n readonly total: number;\n readonly type: 'move';\n }\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'drop' }\n | { readonly index: number; readonly itemId: string; readonly total: number; readonly type: 'cancel' };\n\nexport interface SortableScopeOptions {\n /**\n * Called exactly once for every successful cross-container move.\n * Local reorders continue to use each sortable's `onReorder` callback.\n */\n onMove?: (event: SortableMoveEvent) => void;\n /**\n * Enables touch input for sortable items registered to this scope.\n * The controller ignores unrelated document draggables.\n */\n touch?: boolean | TouchInputOptions;\n}\n\nexport type SortableTouchOptions = TouchInputOptions;\n\nexport interface SortableOptions {\n /** Auto-scrolls the container (and viewport) near edges while dragging. @default true */\n autoScroll?: boolean | AutoScrollOptions;\n /** Sorting axis used to compute insertion position. @default 'vertical' */\n axis?: 'vertical' | 'horizontal';\n /**\n * When `true`, drag interactions are ignored.\n *\n * Note: if `disabled` transitions to `true` while a drag is in progress the\n * drag is treated as a cancellation — the item snaps back to its original\n * position rather than committing the last placeholder location.\n */\n disabled?: boolean;\n /** Optional custom drag preview element. */\n dragImage?: HTMLElement | ((id: string, item: HTMLElement, event: DragEvent) => HTMLElement | null | undefined);\n /**\n * Hotspot offset `[x, y]` passed to `setDragImage`.\n * Controls which point of the preview image follows the cursor.\n * @default [0, 0]\n */\n dragImageOffset?: [number, number];\n /** Container element whose direct-child items are sortable. */\n element: HTMLElement;\n /**\n * Returns the identity key for a given item element.\n * This separates the \"what is this item?\" concern (yours) from the \"which children\n * are sortable?\" concern (ours — marked with `data-dnd-item`).\n *\n * @example\n * ```ts\n * getKey: (el) => el.dataset.taskId!\n * ```\n */\n getKey: (element: HTMLElement) => string;\n /**\n * Selector for the drag handle inside each item.\n * When omitted the whole item is the handle.\n */\n handle?: string;\n /**\n * Returns the current sortable item elements. When omitted the sortable\n * scans the container's direct children. Provide this for explicit item\n * ownership — e.g. when items are managed by a framework render loop.\n */\n items?: () => readonly HTMLElement[];\n /**\n * Enables keyboard-based reordering using arrow keys plus Home/End.\n * @default true\n */\n keyboard?: boolean;\n /**\n * Called just before a successful drag commit with the before and after order snapshots.\n * Use this hook to set up FLIP animations — the source items are still in their\n * pre-commit positions at the time of the call.\n *\n * @example\n * ```ts\n * onBeforeReorder: (from, to) => {\n * // record element positions here, then animate after the next microtask\n * }\n * ```\n */\n onBeforeReorder?: (from: readonly string[], to: readonly string[]) => void;\n /** Called when a drag ends (whether dropped or cancelled). */\n onDragEnd?: (id: string, event: DragEvent) => void;\n /** Called when the user starts dragging an item. */\n onDragStart?: (id: string, event: DragEvent) => void;\n /**\n * Structured accessibility event for pickup/move/drop/cancel — wire to a consumer-side\n * announcer for screen-reader feedback. See {@link SortableInteractionEvent}.\n */\n onInteraction?: (event: SortableInteractionEvent) => void;\n /**\n * Called with a {@link ReorderEvent} after a successful reorder, only when the order changed.\n * The event carries `before`, `after`, and `item` so application history can own rollback.\n *\n * @example\n * ```ts\n * onReorder: ({ before, after, item }) => {\n * history.push({ revert: () => setOrder(before) });\n * setOrder(after);\n * },\n * ```\n */\n onReorder?: (event: ReorderEvent) => void;\n /** CSS class applied to the placeholder element. @default 'dnd-placeholder' */\n placeholderClass?: string;\n /** Shared scope for connected sortable containers. Containers only exchange items within the same scope. */\n scope?: SortableScope;\n}\n\nexport interface Sortable extends Disposable {\n readonly isDragging: boolean;\n /**\n * Re-reads items from the `items` provider (or the container's direct children)\n * and reapplies `draggable`, ARIA roles, and handle attributes. Call this after\n * programmatically adding, removing, or replacing items — e.g. after a framework\n * render that replaces DOM nodes.\n *\n * Not needed when items are only reordered via drag or keyboard.\n */\n refresh(): void;\n}\n\n// ─── Internal types ───────────────────────────────────────────────────────────\n\ninterface ResolvedAutoScrollOptions {\n container: boolean;\n edgeThreshold: number;\n speed: number;\n viewport: boolean;\n}\n\n/** Per-container closure passed into shared session functions. */\ninterface ContainerHandle {\n commitReorder: (event: { after: readonly string[]; before: readonly string[]; item: string }) => void;\n readonly element: HTMLElement;\n getOrderedIds: () => string[];\n getOrderedItems: () => HTMLElement[];\n isDisabled: () => boolean;\n notifyBeforeReorder: (from: readonly string[], to: readonly string[]) => void;\n notifyDragEnd: (id: string, event: DragEvent) => void;\n notifyDragStart: (id: string, event: DragEvent) => void;\n notifyInteraction: (event: SortableInteractionEvent) => void;\n resolveTouchTarget: (target: Element) => HTMLElement | null;\n}\n\n/**\n * Encapsulates all mutable state for a single in-progress drag operation.\n * Created at dragstart, destroyed on dragend/cancel.\n */\ninterface DragSession {\n draggedEl: HTMLElement;\n draggedId: string;\n /** rAF handle for the deferred element-hide; null once fired or cancelled. */\n hideFrame: number | null;\n /**\n * Order snapshots captured lazily: snapshot is taken the first time a handle\n * becomes the active target (and always for the source at drag start).\n */\n initialOrders: Map<ContainerHandle, string[]>;\n originalDisplay: string;\n originalNextSibling: ChildNode | null;\n originalParent: HTMLElement;\n placeholder: HTMLElement;\n source: ContainerHandle;\n target: ContainerHandle | null;\n}\n\ninterface SortableScopeState {\n active: DragSession | null;\n commitMove: (event: SortableMoveEvent) => void;\n /** All sortables registered in this scope, kept for scope.dispose(). */\n disposables: Set<() => void>;\n handles: Set<ContainerHandle>;\n touch: ScopeTouchController | null;\n}\n\ninterface ManagedElementState {\n dataDndHandle: string | null;\n dataDndItem: string | null;\n draggable: string | null;\n role: string | null;\n tabIndex: string | null;\n touchAction: string;\n}\n\ninterface TouchDragEvent extends DragEvent {\n readonly __dndTouch?: boolean;\n readonly __dndTouchPreview?: boolean;\n}\n\n// ─── Internal constants ───────────────────────────────────────────────────────\n\nconst HANDLE_ATTR = 'data-dnd-handle';\nconst ITEM_ATTR = 'data-dnd-item';\nconst sortableScopeStates = new WeakMap<SortableScope, SortableScopeState>();\nconst DEFAULT_AUTO_SCROLL: ResolvedAutoScrollOptions = {\n container: true,\n edgeThreshold: 32,\n speed: 18,\n viewport: false,\n};\n\n// ─── Scope helpers ────────────────────────────────────────────────────────────\n\nfunction getSortableScopeState(scope: SortableScope): SortableScopeState {\n const state = sortableScopeStates.get(scope);\n\n if (!state) throw new DndScopeError();\n if (scope.disposed) throw new DndScopeError('Cannot register a sortable with a disposed scope.');\n\n return state;\n}\n\n// ─── Option resolution ────────────────────────────────────────────────────────\n\nfunction resolveAutoScrollOptions(\n autoScroll: boolean | AutoScrollOptions | undefined,\n): ResolvedAutoScrollOptions | null {\n if (autoScroll === false) return null;\n\n if (autoScroll === true || autoScroll === undefined) return DEFAULT_AUTO_SCROLL;\n if (\n !Number.isFinite(autoScroll.edgeThreshold ?? 32) ||\n (autoScroll.edgeThreshold ?? 32) < 0 ||\n !Number.isFinite(autoScroll.speed ?? 18) ||\n (autoScroll.speed ?? 18) <= 0\n ) {\n throw new DndError('Auto-scroll edgeThreshold must be non-negative and speed must be positive');\n }\n\n return {\n container: autoScroll.container ?? true,\n edgeThreshold: autoScroll.edgeThreshold ?? 32,\n speed: autoScroll.speed ?? 18,\n viewport: autoScroll.viewport ?? false,\n };\n}\n\n// ─── DOM helpers ──────────────────────────────────────────────────────────────\n\nfunction hasOrderChanged(before: string[], after: string[]): boolean {\n return after.length !== before.length || after.some((id, index) => id !== before[index]);\n}\n\nfunction isTouchDragEvent(event: DragEvent): event is TouchDragEvent {\n return (event as TouchDragEvent).__dndTouch === true;\n}\n\nfunction immutableIds(ids: readonly string[]): readonly string[] {\n return Object.freeze([...ids]);\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n// ─── Drag session helpers ─────────────────────────────────────────────────────\n\nfunction scheduleHide(session: DragSession): void {\n if (typeof requestAnimationFrame !== 'function') return;\n\n session.hideFrame = requestAnimationFrame(() => {\n session.hideFrame = null;\n session.draggedEl.style.display = 'none';\n });\n}\n\nfunction cancelHide(session: DragSession): void {\n if (session.hideFrame !== null) {\n cancelAnimationFrame(session.hideFrame);\n session.hideFrame = null;\n }\n}\n\nfunction restoreSessionElement(session: DragSession): void {\n cancelHide(session);\n session.draggedEl.style.display = session.originalDisplay;\n session.draggedEl.removeAttribute('data-dragging');\n}\n\n/**\n * Snapshot a handle's current order if not already recorded.\n * Called the first time a handle becomes an active target so we only capture\n * lists that actually participate in the drag.\n */\nfunction snapshotOrder(session: DragSession, handle: ContainerHandle): void {\n if (!session.initialOrders.has(handle)) {\n session.initialOrders.set(handle, handle.getOrderedIds());\n }\n}\n\nfunction projectedChanges(session: DragSession): Array<{ after: string[]; before: string[]; handle: ContainerHandle }> {\n const target = session.target;\n\n return [...session.initialOrders].flatMap(([handle, before]) => {\n const after = before.filter((id) => id !== session.draggedId);\n\n if (handle === target && session.placeholder.parentElement === handle.element) {\n const children = [...handle.element.children];\n const placeholderIndex = children.indexOf(session.placeholder);\n const insertionIndex = handle\n .getOrderedItems()\n .filter((item) => item !== session.draggedEl && children.indexOf(item) < placeholderIndex).length;\n after.splice(insertionIndex, 0, session.draggedId);\n }\n\n return hasOrderChanged(before, after) ? [{ after, before, handle }] : [];\n });\n}\n\n// ─── Session commit / cancel ──────────────────────────────────────────────────\n\nfunction cancelSession(scopeState: SortableScopeState, event: DragEvent): void {\n const session = scopeState.active;\n\n if (!session) return;\n\n restoreSessionElement(session);\n session.originalParent.insertBefore(\n session.draggedEl,\n session.originalNextSibling?.parentNode === session.originalParent ? session.originalNextSibling : null,\n );\n session.placeholder.remove();\n scopeState.active = null;\n\n session.source.notifyDragEnd(session.draggedId, event);\n session.source.notifyInteraction({\n index: session.source.getOrderedIds().indexOf(session.draggedId),\n itemId: session.draggedId,\n total: session.source.getOrderedIds().length,\n type: 'cancel',\n });\n}\n\nfunction commitSession(scopeState: SortableScopeState, event: DragEvent): void {\n const session = scopeState.active;\n\n if (!session) return;\n\n const targetHandle = session.target;\n const targetElement = session.placeholder.parentElement;\n const projected = projectedChanges(session);\n\n for (const { after, before, handle } of projected) {\n handle.notifyBeforeReorder(immutableIds(before), immutableIds(after));\n }\n\n restoreSessionElement(session);\n\n if (targetHandle && scopeState.handles.has(targetHandle) && targetElement) {\n targetElement.insertBefore(session.draggedEl, session.placeholder);\n } else {\n session.originalParent.insertBefore(\n session.draggedEl,\n session.originalNextSibling?.parentNode === session.originalParent ? session.originalNextSibling : null,\n );\n }\n\n session.placeholder.remove();\n scopeState.active = null;\n\n session.source.notifyDragEnd(session.draggedId, event);\n\n const changes: Array<{ after: readonly string[]; before: readonly string[]; handle: ContainerHandle }> = [];\n\n for (const [handle, before] of session.initialOrders) {\n if (!scopeState.handles.has(handle)) continue;\n\n const afterOrder = handle.getOrderedIds();\n\n if (hasOrderChanged(before, afterOrder)) {\n changes.push({ after: afterOrder, before, handle });\n }\n }\n\n if (targetHandle && targetHandle !== session.source) {\n const sourceChange = changes.find((change) => change.handle === session.source);\n const targetChange = changes.find((change) => change.handle === targetHandle);\n\n if (sourceChange && targetChange) {\n scopeState.commitMove({\n itemId: session.draggedId,\n source: session.source.element,\n sourceBeforeIds: sourceChange.before,\n sourceIds: sourceChange.after,\n target: targetHandle.element,\n targetBeforeIds: targetChange.before,\n targetIds: targetChange.after,\n });\n\n const dropIndex = targetChange.after.indexOf(session.draggedId);\n const dropTotal = targetChange.after.length;\n\n // Emit from both source and target so a consumer wiring onInteraction on\n // either container receives the drop event.\n session.source.notifyInteraction({ index: dropIndex, itemId: session.draggedId, total: dropTotal, type: 'drop' });\n targetHandle.notifyInteraction({ index: dropIndex, itemId: session.draggedId, total: dropTotal, type: 'drop' });\n }\n\n return;\n }\n\n for (const { after, before, handle } of changes) {\n handle.commitReorder({ after, before, item: session.draggedId });\n }\n\n // Same-container — emit drop even when the order didn't change (user picked up\n // and dropped at the same position). The interaction completed; silence would\n // leave screen-reader users without confirmation.\n const sourceChange = changes.find((change) => change.handle === session.source);\n const finalOrder = sourceChange ? sourceChange.after : session.source.getOrderedIds();\n const finalIndex = finalOrder.indexOf(session.draggedId);\n\n session.source.notifyInteraction({\n index: finalIndex,\n itemId: session.draggedId,\n total: finalOrder.length,\n type: 'drop',\n });\n}\n\nfunction finishSession(scopeState: SortableScopeState, event: DragEvent, forceCancel: boolean): void {\n if (!scopeState.active) return;\n\n const { active } = scopeState;\n\n // If source or target became disabled during the drag, treat as cancellation.\n if (\n forceCancel ||\n !event.dataTransfer ||\n event.dataTransfer.dropEffect === 'none' ||\n !active.target ||\n !scopeState.handles.has(active.target) ||\n active.source.isDisabled() ||\n active.target.isDisabled()\n ) {\n cancelSession(scopeState, event);\n } else {\n commitSession(scopeState, event);\n }\n}\n\nfunction maybeAutoScroll(\n event: DragEvent,\n container: HTMLElement,\n axis: 'vertical' | 'horizontal',\n options: ResolvedAutoScrollOptions | null,\n): void {\n if (!options) return;\n\n const { container: scrollContainer, edgeThreshold: threshold, speed, viewport } = options;\n const rect = container.getBoundingClientRect();\n\n if (axis === 'vertical') {\n if (scrollContainer && event.clientY < rect.top + threshold) {\n container.scrollTop -= speed;\n } else if (scrollContainer && event.clientY > rect.bottom - threshold) {\n container.scrollTop += speed;\n }\n\n if (viewport && event.clientY < threshold) {\n window.scrollBy({ left: 0, top: -speed });\n } else if (viewport && event.clientY > window.innerHeight - threshold) {\n window.scrollBy({ left: 0, top: speed });\n }\n\n return;\n }\n\n if (scrollContainer && event.clientX < rect.left + threshold) {\n container.scrollLeft -= speed;\n } else if (scrollContainer && event.clientX > rect.right - threshold) {\n container.scrollLeft += speed;\n }\n\n if (viewport && event.clientX < threshold) {\n window.scrollBy({ left: -speed, top: 0 });\n } else if (viewport && event.clientX > window.innerWidth - threshold) {\n window.scrollBy({ left: speed, top: 0 });\n }\n}\n\n// ─── Keyboard reorder ─────────────────────────────────────────────────────────\n\nfunction keyboardTargetIndex(\n items: HTMLElement[],\n item: HTMLElement,\n event: KeyboardEvent,\n axis: 'vertical' | 'horizontal',\n): number | null {\n const currentIndex = items.indexOf(item);\n\n if (currentIndex < 0) return null;\n\n const forwardKey = axis === 'vertical' ? 'ArrowDown' : 'ArrowRight';\n const backwardKey = axis === 'vertical' ? 'ArrowUp' : 'ArrowLeft';\n const isForward = matchKey(event, forwardKey);\n const isBackward = matchKey(event, backwardKey);\n let targetIndex: number;\n\n if (isForward) targetIndex = Math.min(items.length - 1, currentIndex + 1);\n else if (isBackward) targetIndex = Math.max(0, currentIndex - 1);\n else if (matchKey(event, 'Home')) targetIndex = 0;\n else if (matchKey(event, 'End')) targetIndex = items.length - 1;\n else return null;\n\n // Already at the boundary — return null so the caller does not call preventDefault\n // and the browser can handle the key (e.g. scrolling the page).\n return targetIndex === currentIndex ? null : targetIndex;\n}\n\n// ─── createSortableScope ──────────────────────────────────────────────────────\n\n/**\n * Create a shared scope for connected sortable containers.\n *\n * Items can be dragged between all sortables that share the same scope.\n *\n * @example\n * ```ts\n * const scope = createSortableScope();\n * const left = createSortable({ element: leftEl, scope, getKey, onReorder });\n * const right = createSortable({ element: rightEl, scope, getKey, onReorder });\n *\n * // Tear down everything at once:\n * scope.dispose();\n * ```\n */\nexport function createSortableScope(options: SortableScopeOptions = {}): SortableScope {\n const state: SortableScopeState = {\n active: null,\n commitMove(event): void {\n notify(\n 'onMove',\n options.onMove,\n Object.freeze({\n ...event,\n sourceBeforeIds: immutableIds(event.sourceBeforeIds),\n sourceIds: immutableIds(event.sourceIds),\n targetBeforeIds: immutableIds(event.targetBeforeIds),\n targetIds: immutableIds(event.targetIds),\n }),\n );\n },\n disposables: new Set(),\n handles: new Set(),\n touch: null,\n };\n const disposable = createDisposable(() => {\n state.touch?.dispose();\n\n // Dispose all registered sortables (each dispose() call is idempotent)\n for (const disposeFn of [...state.disposables]) {\n disposeFn();\n }\n state.disposables.clear();\n state.handles.clear();\n });\n\n const scope = {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get isDragging() {\n return state.active !== null;\n },\n [SCOPE_BRAND]: true as const,\n [Symbol.dispose]: disposable[Symbol.dispose],\n } as SortableScope;\n\n sortableScopeStates.set(scope, state);\n\n if (options.touch) {\n state.touch = createScopeTouchController(options.touch === true ? {} : options.touch, (target) => {\n if (state.active) return null;\n for (const handle of state.handles) {\n const dragTarget = handle.resolveTouchTarget(target);\n\n if (dragTarget) return dragTarget;\n }\n\n return null;\n });\n }\n\n return scope;\n}\n\n// ─── createSortable ───────────────────────────────────────────────────────────\n\n/**\n * Makes a list of items sortable via native HTML drag interactions.\n *\n * Provide `getKey` to map each item element to a stable string identity.\n * `createSortable` marks qualifying children with `data-dnd-item` and sets\n * `draggable=\"true\"` automatically.\n *\n * Style the drop indicator by targeting `.dnd-placeholder` in your CSS.\n * Supports keyboard reordering (arrow keys, Home/End) and auto-scrolling near edges.\n *\n * @example\n * ```ts\n * import { createSortable } from '@vielzeug/dnd/sortable';\n *\n * using sortable = createSortable({\n * element: listEl,\n * getKey: (el) => el.dataset.id!,\n * onReorder: ({ before, after }) => {\n * history.push({ revert: () => setOrder(before) });\n * setOrder(after);\n * },\n * });\n * ```\n */\nexport function createSortable(options: SortableOptions): Sortable {\n const {\n autoScroll = true,\n axis = 'vertical',\n element,\n getKey,\n handle,\n keyboard = true,\n placeholderClass = 'dnd-placeholder',\n scope = createSortableScope(),\n } = options;\n if (axis !== 'vertical' && axis !== 'horizontal') throw new DndError('axis must be \"vertical\" or \"horizontal\"');\n const handleSelector = handle?.trim() || null;\n\n if (handle !== undefined && !handleSelector) {\n warn(\n 'handle option is an empty string — no handle elements will be found. Provide a valid CSS selector or omit the option.',\n );\n } else if (handleSelector) {\n try {\n element.querySelector(handleSelector);\n } catch (error) {\n throw new DndError(`Invalid handle selector: \"${handle}\"`, { cause: error });\n }\n }\n\n const autoScrollOptions = resolveAutoScrollOptions(autoScroll);\n const scopeState = getSortableScopeState(scope);\n if ([...scopeState.handles].some((registered) => registered.element === element)) {\n throw new DndError('A sortable is already registered for this element in the selected scope.');\n }\n const getItems = (): HTMLElement[] =>\n (options.items ? options.items() : (Array.from(element.children) as HTMLElement[])).filter(\n (item) => item.parentElement === element && item.hasAttribute(ITEM_ATTR),\n );\n\n const getOrderedIds = (): string[] => getItems().map((el) => getKey(el));\n const managedElements = new Map<HTMLElement, ManagedElementState>();\n const originalContainerRole = element.getAttribute('role');\n\n const rememberElement = (managedElement: HTMLElement): ManagedElementState => {\n const existing = managedElements.get(managedElement);\n\n if (existing) return existing;\n\n const state: ManagedElementState = {\n dataDndHandle: managedElement.getAttribute(HANDLE_ATTR),\n dataDndItem: managedElement.getAttribute(ITEM_ATTR),\n draggable: managedElement.getAttribute('draggable'),\n role: managedElement.getAttribute('role'),\n tabIndex: managedElement.getAttribute('tabindex'),\n touchAction: managedElement.style.touchAction,\n };\n\n managedElements.set(managedElement, state);\n\n return state;\n };\n\n const restoreAttribute = (managedElement: HTMLElement, name: string, value: string | null): void => {\n if (value === null) {\n managedElement.removeAttribute(name);\n } else {\n managedElement.setAttribute(name, value);\n }\n };\n\n const syncItems = (items = getItems()): void => {\n items.forEach((el) => {\n const itemState = rememberElement(el);\n\n if (itemState.role === null) el.setAttribute('role', 'listitem');\n\n if (itemState.tabIndex === null) el.tabIndex = 0;\n\n if (handle !== undefined) {\n handleSelector &&\n el.querySelectorAll<HTMLElement>(handleSelector).forEach((handleEl) => {\n rememberElement(handleEl);\n handleEl.setAttribute(HANDLE_ATTR, '');\n handleEl.setAttribute('draggable', 'true');\n if (scopeState.touch) handleEl.style.touchAction = 'none';\n });\n } else {\n el.setAttribute('draggable', 'true');\n // A native mouse drag has no competing gesture to arbitrate; touch does. Without this,\n // a mobile browser can decide the very first bit of finger movement is a page\n // scroll/pan — a decision it makes independently of, and before, this library's own\n // Gesture activation-distance/`preventDefault()` logic ever runs — and hand the rest of the\n // gesture to native scrolling. Once that happens the item never receives the\n // `dragover` sequence needed to update the drop target, so the session ends up\n // committing back to wherever it started: indistinguishable from the drop \"reverting\".\n // `touch-action: none` opts the element out of every default touch gesture from\n // `pointerdown` onward, leaving the whole interaction to this library's own JS.\n if (scopeState.touch) el.style.touchAction = 'none';\n }\n });\n };\n\n const markItems = (): void => {\n const seenKeys = new Set<string>();\n\n // When an explicit items provider is given, use it directly; otherwise scan\n // the container's direct children for elements that have a key.\n const provided = options.items ? options.items() : (Array.from(element.children) as HTMLElement[]);\n const candidates = provided.filter((item) => item instanceof HTMLElement && item.parentElement === element);\n const keyed: HTMLElement[] = [];\n\n if (candidates.length !== provided.length) {\n warn('items returned an element that is not a direct child of the sortable container; it was ignored.');\n }\n\n for (const el of candidates) {\n try {\n const key = getKey(el);\n\n if (key) {\n if (seenKeys.has(key)) {\n warn(\n `getKey returned the duplicate key \"${key}\" for two sibling items — the duplicate item was excluded. Ensure getKey returns a unique value per item.`,\n );\n continue;\n }\n\n seenKeys.add(key);\n keyed.push(el);\n }\n } catch (err) {\n warn(\n `getKey threw for a child element — the item will not be sortable. Check your getKey implementation. ${String(err)}`,\n );\n }\n }\n\n cleanupItems();\n for (const el of keyed) {\n rememberElement(el);\n el.setAttribute(ITEM_ATTR, '');\n }\n syncItems(keyed);\n };\n\n const cleanupItems = (): void => {\n for (const [managedElement, state] of managedElements) {\n restoreAttribute(managedElement, HANDLE_ATTR, state.dataDndHandle);\n restoreAttribute(managedElement, ITEM_ATTR, state.dataDndItem);\n restoreAttribute(managedElement, 'draggable', state.draggable);\n restoreAttribute(managedElement, 'role', state.role);\n restoreAttribute(managedElement, 'tabindex', state.tabIndex);\n managedElement.style.touchAction = state.touchAction;\n }\n\n managedElements.clear();\n };\n\n const createPlaceholder = (source: HTMLElement): HTMLElement => {\n const p = document.createElement('div');\n\n p.className = placeholderClass;\n p.setAttribute('aria-hidden', 'true');\n\n if (axis === 'horizontal') {\n p.style.width = `${source.offsetWidth}px`;\n } else {\n p.style.height = `${source.offsetHeight}px`;\n }\n\n return p;\n };\n\n const handle_: ContainerHandle = {\n commitReorder: (event) =>\n notify(\n 'onReorder',\n options.onReorder,\n Object.freeze({ after: immutableIds(event.after), before: immutableIds(event.before), item: event.item }),\n ),\n element,\n getOrderedIds,\n getOrderedItems: getItems,\n isDisabled: () => resolveDisabled(options.disabled),\n notifyBeforeReorder: (from, to) => notify('onBeforeReorder', options.onBeforeReorder, from, to),\n notifyDragEnd: (id, event) => notify('onDragEnd', options.onDragEnd, id, event),\n notifyDragStart: (id, event) => notify('onDragStart', options.onDragStart, id, event),\n notifyInteraction: (event) => notify('onInteraction', options.onInteraction, Object.freeze(event)),\n resolveTouchTarget: (target) => {\n if (resolveDisabled(options.disabled) || !element.contains(target)) return null;\n\n const item = target.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!item || !getItems().includes(item)) return null;\n\n if (handle === undefined) return item;\n if (!handleSelector) return null;\n\n const handleTarget = target.closest<HTMLElement>(handleSelector);\n\n return handleTarget && item.contains(handleTarget) ? handleTarget : null;\n },\n };\n\n const handleDragStart = (e: DragEvent): void => {\n if (scopeState.active) return;\n\n if (handle_.isDisabled()) return;\n\n const target = e.target as HTMLElement;\n const item = target.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!item || !getItems().includes(item)) return;\n\n if (handle !== undefined) {\n const handleTarget = handleSelector ? target.closest(handleSelector) : null;\n if (!handleTarget || !item.contains(handleTarget)) return;\n }\n\n const originalParent = item.parentElement;\n\n if (!originalParent) return;\n\n const placeholder = createPlaceholder(item);\n const originalNextSibling = item.nextSibling;\n const activeId = getKey(item);\n\n // Snapshot only the source handle at drag start; targets are snapshotted lazily.\n const initialOrders = new Map<ContainerHandle, string[]>();\n\n initialOrders.set(handle_, handle_.getOrderedIds());\n item.setAttribute('data-dragging', '');\n originalParent.insertBefore(placeholder, originalNextSibling);\n\n const session: DragSession = {\n draggedEl: item,\n draggedId: activeId,\n hideFrame: null,\n initialOrders,\n originalDisplay: item.style.display,\n originalNextSibling,\n originalParent,\n placeholder,\n source: handle_,\n target: handle_,\n };\n\n if (!isTouchDragEvent(e) || e.__dndTouchPreview) scheduleHide(session);\n\n scopeState.active = session;\n\n if (e.dataTransfer) {\n try {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', activeId);\n\n if (options.dragImage) {\n const preview =\n typeof options.dragImage === 'function' ? options.dragImage(activeId, item, e) : options.dragImage;\n const [offsetX, offsetY] = options.dragImageOffset ?? [0, 0];\n\n if (preview) e.dataTransfer.setDragImage(preview, offsetX, offsetY);\n }\n } catch (error) {\n warn(`drag preview setup failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n handle_.notifyDragStart(session.draggedId, e);\n handle_.notifyInteraction({\n index: handle_.getOrderedIds().indexOf(activeId),\n itemId: activeId,\n total: handle_.getOrderedIds().length,\n type: 'pickup',\n });\n };\n\n const handleDragOver = (e: DragEvent): void => {\n const session = scopeState.active;\n\n if (!session) return;\n\n if (session.source.isDisabled() || handle_.isDisabled()) return;\n\n e.preventDefault();\n maybeAutoScroll(e, element, axis, autoScrollOptions);\n\n // Lazily snapshot this handle's order the first time it becomes a target.\n snapshotOrder(session, handle_);\n\n const { draggedEl, placeholder } = session;\n const target = (e.target as HTMLElement).closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (target && !getItems().includes(target)) return;\n\n if (!target) {\n // Only append placeholder when it isn't already inside this container.\n // Moving it to the end on every over-empty-space event causes the\n // placeholder to oscillate between positions as the cursor moves.\n if (placeholder.parentElement !== element) {\n element.appendChild(placeholder);\n }\n\n session.target = handle_;\n\n return;\n }\n\n if (target === draggedEl || target === placeholder) return;\n\n const rect = target.getBoundingClientRect();\n const insertAfter =\n axis === 'vertical' ? e.clientY >= rect.top + rect.height / 2 : e.clientX >= rect.left + rect.width / 2;\n\n element.insertBefore(placeholder, insertAfter ? target.nextSibling : target);\n session.target = handle_;\n };\n\n const handleDrop = (e: DragEvent): void => {\n const session = scopeState.active;\n\n if (!session) return;\n\n if (session.source.isDisabled() || handle_.isDisabled()) return;\n\n e.preventDefault();\n // Record the drop target; the actual commit happens in handleDragEnd where\n // dataTransfer.dropEffect tells us whether the browser accepted the operation.\n session.target = handle_;\n };\n\n const handleDragEnd = (e: DragEvent): void => {\n if (scopeState.active?.source !== handle_) return;\n\n finishSession(scopeState, e, false);\n };\n\n const handleKeydown = (e: KeyboardEvent): void => {\n if (!keyboard || handle_.isDisabled()) return;\n\n const eventTarget = e.target as HTMLElement | null;\n const item = eventTarget?.closest<HTMLElement>(`[${ITEM_ATTR}]`);\n\n if (!eventTarget || !item || !getItems().includes(item)) return;\n\n const interactive = eventTarget.closest<HTMLElement>(\n 'input, textarea, select, button, a[href], [contenteditable]:not([contenteditable=\"false\"]), [role=\"button\"], [role=\"link\"]',\n );\n const keyboardHandle = handleSelector ? eventTarget.closest<HTMLElement>(handleSelector) : null;\n\n if (interactive && (!keyboardHandle || !item.contains(keyboardHandle))) return;\n\n const items = getItems();\n const prevIndex = items.indexOf(item);\n const targetIndex = keyboardTargetIndex(items, item, e, axis);\n\n // null means unrecognized key or boundary — let the browser handle it (e.g. page scroll)\n if (targetIndex === null) return;\n\n const targetItem = items[targetIndex];\n if (!targetItem) return;\n\n const prevOrder = items.map(getKey);\n const reorderedItems = [...items];\n reorderedItems.splice(prevIndex, 1);\n reorderedItems.splice(targetIndex, 0, item);\n const newOrder = reorderedItems.map(getKey);\n const activeId = getKey(item);\n\n e.preventDefault();\n handle_.notifyBeforeReorder(immutableIds(prevOrder), immutableIds(newOrder));\n element.insertBefore(item, targetIndex > prevIndex ? targetItem.nextSibling : targetItem);\n item.focus();\n handle_.commitReorder({ after: newOrder, before: prevOrder, item: activeId });\n handle_.notifyInteraction({\n index: targetIndex,\n itemId: activeId,\n previousIndex: prevIndex,\n total: newOrder.length,\n type: 'move',\n });\n };\n\n markItems();\n\n let stopTouch: (() => void) | undefined;\n const disposable = createDisposable(() => {\n scopeState.disposables.delete(disposable.dispose);\n\n if (\n scopeState.active &&\n (scopeState.active.source === handle_ || scopeState.active.target === handle_) &&\n !scopeState.touch?.cancel()\n ) {\n finishSession(scopeState, new Event('dragend') as DragEvent, true);\n }\n\n stopTouch?.();\n scopeState.handles.delete(handle_);\n restoreAttribute(element, 'role', originalContainerRole);\n cleanupItems();\n });\n\n scopeState.handles.add(handle_);\n stopTouch = scopeState.touch?.register(element);\n\n if (originalContainerRole === null) element.setAttribute('role', 'list');\n\n element.addEventListener('dragstart', handleDragStart, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n element.addEventListener('dragend', handleDragEnd, { signal: disposable.disposalSignal });\n element.addEventListener('keydown', handleKeydown, { signal: disposable.disposalSignal });\n\n // Register with scope so scope.dispose() can tear this down\n scopeState.disposables.add(disposable.dispose);\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get isDragging() {\n return scopeState.active?.source === handle_;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n refresh: () => {\n markItems();\n },\n };\n}\n\n// ─── applyReorder ─────────────────────────────────────────────────────────────\n\n/**\n * Applies a sorted key array to a backing data array.\n * Unknown keys are ignored; omitted items are appended and duplicate backing keys throw `DndError`.\n */\nexport function applyReorder<T>(items: readonly T[], ids: readonly string[], getKey: (item: T) => string): T[] {\n const byId = new Map<string, T>();\n\n for (const item of items) {\n const id = getKey(item);\n if (byId.has(id)) throw new DndError(`Duplicate item key: \"${id}\"`);\n byId.set(id, item);\n }\n const ordered: T[] = [];\n\n for (const id of ids) {\n if (!byId.has(id)) continue;\n\n const item = byId.get(id) as T;\n\n ordered.push(item);\n byId.delete(id);\n }\n\n for (const item of byId.values()) ordered.push(item);\n\n return ordered;\n}\n\nexport { DndError, DndScopeError } from './errors.js';\nexport type { Disposable } from './types.js';\n"],"mappings":"kGCUA,SAAgB,iBAAiB,EAAkC,CACjE,IAAI,EAAW,GACT,EAAkB,IAAI,gBAEtB,YAAsB,CACtB,IAEJ,EAAW,GACX,EAAgB,MAAM,EACtB,IAAU,EACZ,EAEA,MAAO,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAgB,MACzB,EACA,QACA,IAAI,UAAW,CACb,OAAO,CACT,GACC,OAAO,SAAU,OACpB,CACF,CAIA,SAAgB,gBAAgB,EAAwC,CACtE,OAAO,IAAa,EACtB,CCrCA,IAAa,SAAb,cAA8B,KAAM,CAClC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,cAAb,cAAmC,QAAS,CAC1C,YAAY,EAAU,8DAA+D,CACnF,MAAM,CAAO,CACf,CACF,ECGA,SAAgB,cAAc,EAAY,EAAoC,CAG5E,MAFA,CAAK,EAAO,QAEL,EAAO,KAAM,GAAY,CAC9B,IAAM,EAAI,EAAQ,KAAK,CAAC,CAAC,YAAY,EAC/B,EAAO,EAAK,KAAK,YAAY,EAMnC,OAJK,EACD,EAAE,WAAW,GAAG,EAAU,EAAK,KAAK,YAAY,CAAC,CAAC,SAAS,CAAC,EAC5D,EAAE,SAAS,IAAI,EAAU,EAAK,WAAW,EAAE,MAAM,EAAG,EAAE,CAAC,EAEpD,IAAS,EAJD,EAKjB,CAAC,CACH,CA4GA,SAAS,iBAAiB,EAA6B,EAA2B,CAGhF,MAFA,CAAK,EAAO,QAEL,MAAM,KAAK,CAAK,CAAC,CAAC,KACtB,GACC,EAAK,OAAS,QACd,EAAO,KAAM,GAAY,CACvB,IAAM,EAAI,EAAQ,KAAK,EAMvB,OAJI,EAAE,SAAS,IAAI,EAAU,EAAK,KAAK,WAAW,EAAE,MAAM,EAAG,EAAE,CAAC,EAE5D,EAAE,WAAW,GAAG,EAAU,GAEvB,EAAK,OAAS,CACvB,CAAC,CACL,CACF,CAMA,SAAS,iBACP,EACA,EACA,EACwC,CACxC,IAAM,EAAmB,CAAC,EACpB,EAAmB,CAAC,EAE1B,IAAK,IAAM,KAAK,GACb,cAAc,EAAG,CAAM,EAAI,EAAW,EAAA,CAAU,KAAK,CAAC,EAOzD,OAJI,IAAa,IAAA,IAAa,EAAS,OAAS,GAC9C,EAAS,KAAK,GAAG,EAAS,OAAO,CAAQ,CAAC,EAGrC,CAAE,WAAU,UAAS,CAC9B,CAIA,SAAS,gBAAgB,EAAuC,CAC9D,OAAO,EAAS,IAAK,GAAY,CAC/B,IAAM,EAAa,EAAQ,KAAK,CAAC,CAAC,YAAY,EACxC,EAAY,4BAA4B,KAAK,CAAU,EACvD,EAAO,kDAAkD,KAAK,CAAU,EAE9E,GAAI,CAAC,GAAa,CAAC,EAAM,MAAM,IAAI,SAAS,4BAA4B,EAAQ,EAAE,EAClF,OAAO,CACT,CAAC,CACH,CAEA,SAAS,SACP,EACA,EACA,GAAG,EACG,CACD,KACL,GAAI,CACF,EAAS,GAAG,CAAI,CAClB,MAAQ,CACD,GAAG,EAAH,CACP,CACF,CAuBA,SAAgB,eAAe,EAAoC,CACjE,IAAM,EAAS,gBAAgB,EAAQ,QAAU,CAAC,CAAC,EACnD,GAAI,EAAQ,WAAa,IAAA,KAAc,CAAC,OAAO,cAAc,EAAQ,QAAQ,GAAK,EAAQ,SAAW,GACnG,MAAM,IAAI,SAAS,8CAA8C,EAGnE,GAAM,CAAE,aAAa,OAAQ,UAAS,WAAU,SAAQ,iBAAgB,gBAAe,sBAAuB,EAE1G,EAAc,EAGd,EAAe,GACf,EAAa,GACX,EAAwB,IAAI,IAE5B,cAAiB,GAAwB,CACzC,IAAe,IAEnB,EAAa,EACb,SAAO,qBAAsB,EAAoB,CAAI,EACvD,EAEM,cAAiB,GAAuB,CAC5C,IAAM,EAAa,EAAc,GAAK,EAEtC,EAAc,KAAK,IAAI,EAAG,CAAI,EAG1B,IAAgB,IAAG,EAAe,IAEtC,IAAM,EAAU,EAAc,GAAK,EAE/B,IAAY,GAAY,SAAO,gBAAiB,EAAe,CAAO,CAC5E,EAEM,iBAA2B,CAC/B,cAAc,CAAC,CACjB,EAEM,EAAa,qBAAuB,CACxC,IAAK,IAAM,KAAc,EAAuB,EAAW,MAAM,EAEjE,EAAsB,MAAM,EAC5B,cAAc,EAAK,EACnB,aAAa,CACf,CAAC,EAGK,QAAU,EAAuB,IAAgC,CACjE,EAAc,OAAS,GAAG,SAAO,SAAU,EAAQ,CAAC,GAAG,CAAa,CAAC,EACrE,EAAc,OAAS,GAAG,SAAO,iBAAkB,EAAgB,CAAC,GAAG,CAAa,CAAC,CAC3F,EAGM,gBAAkB,EAAuB,IAAgC,CACzE,EAAc,OAAS,IACrB,EAAQ,QACV,SAAO,UAAW,EAAQ,QAAS,CAAC,GAAG,CAAa,CAAC,EAErD,SAAO,SAAU,EAAQ,CAAC,GAAG,CAAa,CAAC,GAI3C,EAAc,OAAS,GAAG,SAAO,iBAAkB,EAAgB,CAAC,GAAG,CAAa,CAAC,CAC3F,EAGM,wBAA0B,EAAkB,IAAiE,CACjH,GAAM,CAAE,WAAU,SAAU,GAAQ,iBAAiB,EAAU,EAAQ,CAAQ,EACzE,EAAa,EAAQ,WACrB,EAAuB,GAAc,EAAS,OAAS,EAAI,IAAI,gBAAoB,KAErF,IACF,EAAsB,IAAI,CAAoB,EAC9C,cAAc,EAAI,GAGpB,IAAM,qBAA+B,CAC9B,IAEL,EAAsB,OAAO,CAAoB,EAE5C,EAAW,UAAU,cAAc,EAAsB,KAAO,CAAC,EACxE,EAEI,EAEJ,GAAI,CACF,EACE,GAAwB,EAAa,EAAW,CAAC,GAAG,CAAQ,EAAG,CAAE,OAAQ,EAAqB,MAAO,CAAC,EAAI,EAC9G,OAAS,EAAO,CACd,EAAa,QAAQ,OAAO,CAAK,CACnC,CAEA,QAAa,QAAQ,CAAU,CAAC,CAAC,KAC9B,GAAU,CACT,iBAAiB,EAEb,GAAW,WAEX,IAAU,GACZ,EAAS,EAAU,CAAG,EAEtB,EAAS,CAAC,EAAG,CAAC,GAAG,EAAK,GAAG,CAAQ,CAAC,EAEtC,MACM,CACJ,iBAAiB,EAEb,GAAW,UAEf,EAAS,CAAC,EAAG,CAAC,GAAG,EAAK,GAAG,CAAQ,CAAC,CACpC,CACF,CACF,EAEM,gBAAmB,GAAuB,CAC1C,oBAAgB,EAAQ,QAAQ,EAMpC,IAJA,EAAE,eAAe,EAIb,IAAgB,EAAG,CACrB,IAAM,EAAQ,EAAE,cAAc,MAE9B,EAAe,CAAC,EAAO,QAAU,CAAC,GAAO,QAAU,iBAAiB,EAAO,CAAM,CACnF,CAEI,CAAC,GAAgB,EAAE,eACrB,EAAE,aAAa,WAAa,QAK9B,cAAc,EAAc,CAAC,CAR7B,CASF,EAEM,eAAkB,GAAuB,CACzC,gBAAgB,EAAQ,QAAQ,IAEpC,EAAE,eAAe,EAEb,EAAE,eAAc,EAAE,aAAa,WAAa,EAAe,EAAa,QAC9E,EAEM,gBAAmB,GAAwB,CAG/C,cAAc,EAAc,CAAC,CAC/B,EAEM,WAAc,GAAuB,CAIzC,GAFA,aAAa,EAET,gBAAgB,EAAQ,QAAQ,EAAG,OAEvC,EAAE,eAAe,EAEjB,IAAM,EAAM,EAAE,cAAc,MAEvB,GAEL,uBAAuB,MAAM,KAAK,CAAG,EAAG,MAAM,CAChD,EAEM,YAAe,GAA4B,CAC/C,GAAI,gBAAgB,EAAQ,QAAQ,EAAG,OAEvC,IAAM,EAAY,EAAE,eAAe,MAE9B,GAAW,SAEhB,EAAE,eAAe,EACjB,uBAAuB,MAAM,KAAK,CAAS,EAAG,cAAc,EAC9D,EAcA,OAZA,EAAQ,iBAAiB,YAAa,gBAAiB,CAAE,OAAQ,EAAW,cAAe,CAAC,EAC5F,EAAQ,iBAAiB,WAAY,eAAgB,CAAE,OAAQ,EAAW,cAAe,CAAC,EAC1F,EAAQ,iBAAiB,YAAa,gBAAiB,CAAE,OAAQ,EAAW,cAAe,CAAC,EAC5F,EAAQ,iBAAiB,OAAQ,WAAY,CAAE,OAAQ,EAAW,cAAe,CAAC,EAE9E,EAAQ,OAAO,EAAQ,iBAAiB,QAAS,YAAa,CAAE,OAAQ,EAAW,cAAe,CAAC,EAIvG,OAAO,iBAAiB,UAAW,aAAc,CAAE,OAAQ,EAAW,cAAe,CAAC,EACtF,OAAO,iBAAiB,OAAQ,aAAc,CAAE,OAAQ,EAAW,cAAe,CAAC,EAE5E,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAW,cACpB,EACA,QAAS,EAAW,QACpB,IAAI,UAAW,CACb,OAAO,EAAW,QACpB,EACA,IAAI,SAAU,CACZ,OAAO,EAAc,GAAK,CAC5B,GACC,OAAO,SAAU,EAAW,OAAO,SACpC,IAAI,YAAa,CACf,OAAO,CACT,CACF,CACF,CCzZA,IAAM,EAAkB,WAUxB,SAAS,kBAAiC,CACxC,MAAO,CACL,WAAY,OACZ,cAAe,OACf,YAAe,GACf,SAAU,CAAC,EACX,cAAe,CAAC,CAClB,CACF,CAEA,SAAS,qBAAqB,EAAkC,CAC9D,IAAM,EAAO,EAAO,sBAAsB,EACpC,EAAU,EAAO,cAAc,cAAc,KAAK,EAkBxD,OAjBA,EAAQ,aAAa,cAAe,MAAM,EAC1C,EAAQ,aAAa,yBAA0B,EAAE,EACjD,EAAQ,aAAa,QAAS,EAAE,EAChC,OAAO,OAAO,EAAQ,MAAO,CAC3B,WAAY,cACZ,OAAQ,yBACR,UAAW,aACX,OAAQ,GAAG,EAAK,OAAO,IACvB,KAAM,GAAG,EAAK,KAAK,IACnB,QAAS,MACT,cAAe,OACf,SAAU,QACV,IAAK,GAAG,EAAK,IAAI,IACjB,WAAY,OACZ,MAAO,GAAG,EAAK,MAAM,IACrB,OAAQ,OAAO,CAAe,CAChC,CAAC,EACM,CACT,CAEA,SAAgB,2BACd,EACA,EACsB,CACtB,GAAM,CAAE,qBAAqB,EAAG,QAAS,GAAkB,EAC3D,GAAI,CAAC,OAAO,SAAS,CAAkB,GAAK,EAAqB,EAC/D,MAAM,IAAI,SAAS,+DAA+D,EAGpF,IAAM,EAAe,iBAAiB,EAChC,EAAW,IAAI,IACjB,EAAoC,KACpC,EAAmC,KAEjC,iBAA2B,CAC/B,GAAS,SAAS,OAAO,EACzB,EAAgB,KAChB,EAAU,IACZ,EACM,EAAa,qBAAuB,CACxC,aAAa,EACb,EAAS,MAAM,CACjB,CAAC,EAEK,YAAe,GAAwC,CAC3D,IAAM,EAAS,EAAM,aAAa,CAAC,CAAC,KAAM,GAAU,EAAc,WAAa,CAAC,GAAK,EAAM,OAC3F,OAAO,GAAW,EAAgB,WAAa,EAAK,EAAqB,IAC3E,EAEM,cAAgB,EAA0B,IAAwD,CACtG,IAAM,EAAwB,EAAO,OAAO,MAAM,QAC5C,EAAyB,EAAO,SAAS,MAAM,SAAW,GAChE,EAAO,OAAO,MAAM,QAAU,OAC1B,EAAO,UAAS,EAAO,QAAQ,MAAM,QAAU,QACnD,GAAI,CACF,OAAO,EAAO,OAAO,cAAc,iBAAiB,EAAM,EAAG,EAAM,CAAC,CACtE,QAAU,CACR,EAAO,OAAO,MAAM,QAAU,EAC1B,EAAO,UAAS,EAAO,QAAQ,MAAM,QAAU,EACrD,CACF,EAEM,UACJ,EACA,EACA,EACA,EAAa,KACD,CAEZ,IAAM,EAAQ,KADI,EAAQ,cAAc,aAAa,QAAS,OAClC,EAAM,CAAE,QAAS,GAAM,WAAY,EAAK,CAAC,EASrE,OARA,OAAO,iBAAiB,EAAO,CAC7B,WAAY,CAAE,aAAc,GAAM,MAAO,EAAK,EAC9C,kBAAmB,CAAE,aAAc,GAAM,MAAO,CAAW,EAC3D,QAAS,CAAE,aAAc,GAAM,MAAO,EAAM,CAAE,EAC9C,QAAS,CAAE,aAAc,GAAM,MAAO,EAAM,CAAE,EAC9C,aAAc,CAAE,aAAc,GAAM,MAAO,CAAa,CAC1D,CAAC,EACD,EAAQ,cAAc,CAAK,EACpB,EAAM,gBACf,EAEM,cAAiB,GAA4C,CACjE,GAAI,IAAkB,GAAO,OAAO,KACpC,IAAI,EACJ,GAAI,CACF,IAAM,EAAU,IAAgB,CAAM,EACtC,EAAiB,EACX,GAAS,UAAU,EAAI,GAAiC,KAC1D,qBAAqB,CAAM,CACjC,OAAS,EAAO,CACT,GAAgE,OAAO,CAAK,EAA5E,EACL,EAAiB,IACnB,CAcA,OAbK,GAEL,EAAe,aAAa,cAAe,MAAM,EACjD,EAAe,aAAa,yBAA0B,EAAE,EACxD,EAAe,aAAa,QAAS,EAAE,EACvC,EAAe,gBAAgB,IAAI,EACnC,EAAe,iBAAiB,MAAM,CAAC,CAAC,QAAS,GAAY,CAC3D,EAAQ,gBAAgB,IAAI,CAC9B,CAAC,EACD,EAAe,MAAM,cAAgB,OACrC,EAAe,MAAM,SAAW,QAChC,EAAe,MAAM,OAAS,OAAO,CAAe,EACpD,EAAO,cAAc,KAAK,YAAY,CAAc,EAC7C,GAbqB,IAc9B,EAEM,MAAS,GAAoC,CACjD,IAAM,EAAS,EAEf,GADA,EAAgB,KACZ,CAAC,EAAQ,OAEb,IAAM,EAAU,cAAc,CAAM,EAQpC,EAAU,CANR,QAAS,EAAO,QAChB,WAAY,EACZ,UACA,cAAe,EAAU,EAAO,QAAU,KAC1C,QAEQ,EACV,SAAS,EAAQ,YAAa,EAAO,QAAS,IAAY,IAAI,CAChE,EAEM,KAAQ,GAAoC,CAChD,GAAI,CAAC,EAAS,OAEd,GADA,EAAQ,QAAU,EAAO,QACrB,EAAQ,SAAW,EAAQ,cAAe,CAC5C,IAAM,EAAI,EAAO,QAAQ,EAAI,EAAQ,cAAc,EAC7C,EAAI,EAAO,QAAQ,EAAI,EAAQ,cAAc,EACnD,EAAQ,QAAQ,MAAM,UAAY,eAAe,EAAE,MAAM,EAAE,OAC7D,CAEA,IAAM,EAAQ,aAAa,EAAS,EAAO,OAAO,EAC9C,GAAS,IAAU,EAAQ,aACzB,EAAQ,YAAY,SAAS,EAAQ,WAAY,YAAa,EAAO,OAAO,EAChF,EAAQ,WAAa,GAEnB,GAAO,SAAS,EAAO,WAAY,EAAO,OAAO,EACrD,EAAO,MAAM,eAAe,CAC9B,EAEM,IAAO,GAAiF,CAE5F,GADA,EAAgB,KACZ,CAAC,EAAS,OACd,IAAM,EAAS,EACf,GAAI,CACF,IAAI,EAAe,GACnB,GAAI,EAAO,SAAW,UAAW,CAC/B,IAAM,EAAQ,aAAa,EAAQ,EAAO,OAAO,EAC7C,IAAO,EAAe,SAAS,EAAO,OAAQ,EAAO,OAAO,EAClE,CACA,EAAa,WAAa,EAAe,OAAS,OAClD,SAAS,EAAO,OAAQ,UAAW,EAAO,QAAS,EAAO,UAAY,IAAI,CAC5E,QAAU,CACR,EAAa,WAAa,OAC1B,aAAa,CACf,CACF,EAEA,OAAO,OAAO,OAAO,EAAY,CAC/B,QAAkB,CAChB,IAAK,IAAM,KAAW,EAAS,OAAO,EACpC,GAAI,EAAQ,OAAO,EAAG,MAAO,GAE/B,MAAO,EACT,EACA,SAAS,EAAkC,CACzC,IAAM,EAAU,EAAkB,EAAS,CACzC,qBACA,MAAO,IACP,OAAQ,KACR,QAAS,MACT,eAAgB,GAChB,YAAc,GAAU,CACtB,GAAI,EAAM,cAAgB,SAAW,EAAS,MAAO,GACrD,IAAM,EAAS,YAAY,CAAK,EAEhC,MADA,GAAgB,EAAS,EAAkB,CAAM,EAAI,KAC9C,IAAkB,IAC3B,EACA,OAAQ,EAAW,cACrB,CAAC,EAGD,OAFA,EAAS,IAAI,EAAS,CAAO,MAEhB,CACX,EAAQ,QAAQ,EAChB,EAAS,OAAO,CAAO,EACnB,GAAiB,EAAQ,SAAS,CAAa,IAAG,EAAgB,MAClE,GAAW,EAAQ,SAAS,EAAQ,MAAM,GAAG,aAAa,CAChE,CACF,CACF,CAAC,CACH,CCtOA,IAAM,EAAc,OAAO,eAAe,EA6QpC,EAAc,kBACd,EAAY,gBACZ,EAAsB,IAAI,QAC1B,EAAiD,CACrD,UAAW,GACX,cAAe,GACf,MAAO,GACP,SAAU,EACZ,EAIA,SAAS,sBAAsB,EAA0C,CACvE,IAAM,EAAQ,EAAoB,IAAI,CAAK,EAE3C,GAAI,CAAC,EAAO,MAAM,IAAI,cACtB,GAAI,EAAM,SAAU,MAAM,IAAI,cAAc,mDAAmD,EAE/F,OAAO,CACT,CAIA,SAAS,yBACP,EACkC,CAClC,GAAI,IAAe,GAAO,OAAO,KAEjC,GAAI,IAAe,IAAQ,IAAe,IAAA,GAAW,OAAO,EAC5D,GACE,CAAC,OAAO,SAAS,EAAW,eAAiB,EAAE,IAC9C,EAAW,eAAiB,IAAM,GACnC,CAAC,OAAO,SAAS,EAAW,OAAS,EAAE,IACtC,EAAW,OAAS,KAAO,EAE5B,MAAM,IAAI,SAAS,2EAA2E,EAGhG,MAAO,CACL,UAAW,EAAW,WAAa,GACnC,cAAe,EAAW,eAAiB,GAC3C,MAAO,EAAW,OAAS,GAC3B,SAAU,EAAW,UAAY,EACnC,CACF,CAIA,SAAS,gBAAgB,EAAkB,EAA0B,CACnE,OAAO,EAAM,SAAW,EAAO,QAAU,EAAM,MAAM,EAAI,IAAU,IAAO,EAAO,EAAM,CACzF,CAEA,SAAS,iBAAiB,EAA2C,CACnE,OAAQ,EAAyB,aAAe,EAClD,CAEA,SAAS,aAAa,EAA2C,CAC/D,OAAO,OAAO,OAAO,CAAC,GAAG,CAAG,CAAC,CAC/B,CAEA,SAAS,OACP,EACA,EACA,GAAG,EACG,CACD,KACL,GAAI,CACF,EAAS,GAAG,CAAI,CAClB,MAAQ,CACD,GAAG,EAAH,CACP,CACF,CAIA,SAAS,aAAa,EAA4B,CAC5C,OAAO,uBAA0B,aAErC,EAAQ,UAAY,0BAA4B,CAC9C,EAAQ,UAAY,KACpB,EAAQ,UAAU,MAAM,QAAU,MACpC,CAAC,EACH,CAEA,SAAS,WAAW,EAA4B,CAC1C,EAAQ,YAAc,OACxB,qBAAqB,EAAQ,SAAS,EACtC,EAAQ,UAAY,KAExB,CAEA,SAAS,sBAAsB,EAA4B,CACzD,WAAW,CAAO,EAClB,EAAQ,UAAU,MAAM,QAAU,EAAQ,gBAC1C,EAAQ,UAAU,gBAAgB,eAAe,CACnD,CAOA,SAAS,cAAc,EAAsB,EAA+B,CACrE,EAAQ,cAAc,IAAI,CAAM,GACnC,EAAQ,cAAc,IAAI,EAAQ,EAAO,cAAc,CAAC,CAE5D,CAEA,SAAS,iBAAiB,EAA6F,CACrH,IAAM,EAAS,EAAQ,OAEvB,MAAO,CAAC,GAAG,EAAQ,aAAa,CAAC,CAAC,SAAS,CAAC,EAAQ,KAAY,CAC9D,IAAM,EAAQ,EAAO,OAAQ,GAAO,IAAO,EAAQ,SAAS,EAE5D,GAAI,IAAW,GAAU,EAAQ,YAAY,gBAAkB,EAAO,QAAS,CAC7E,IAAM,EAAW,CAAC,GAAG,EAAO,QAAQ,QAAQ,EACtC,EAAmB,EAAS,QAAQ,EAAQ,WAAW,EACvD,EAAiB,EACpB,gBAAgB,CAAC,CACjB,OAAQ,GAAS,IAAS,EAAQ,WAAa,EAAS,QAAQ,CAAI,EAAI,CAAgB,CAAC,CAAC,OAC7F,EAAM,OAAO,EAAgB,EAAG,EAAQ,SAAS,CACnD,CAEA,OAAO,gBAAgB,EAAQ,CAAK,EAAI,CAAC,CAAE,QAAO,SAAQ,QAAO,CAAC,EAAI,CAAC,CACzE,CAAC,CACH,CAIA,SAAS,cAAc,EAAgC,EAAwB,CAC7E,IAAM,EAAU,EAAW,OAEtB,IAEL,sBAAsB,CAAO,EAC7B,EAAQ,eAAe,aACrB,EAAQ,UACR,EAAQ,qBAAqB,aAAe,EAAQ,eAAiB,EAAQ,oBAAsB,IACrG,EACA,EAAQ,YAAY,OAAO,EAC3B,EAAW,OAAS,KAEpB,EAAQ,OAAO,cAAc,EAAQ,UAAW,CAAK,EACrD,EAAQ,OAAO,kBAAkB,CAC/B,MAAO,EAAQ,OAAO,cAAc,CAAC,CAAC,QAAQ,EAAQ,SAAS,EAC/D,OAAQ,EAAQ,UAChB,MAAO,EAAQ,OAAO,cAAc,CAAC,CAAC,OACtC,KAAM,QACR,CAAC,EACH,CAEA,SAAS,cAAc,EAAgC,EAAwB,CAC7E,IAAM,EAAU,EAAW,OAE3B,GAAI,CAAC,EAAS,OAEd,IAAM,EAAe,EAAQ,OACvB,EAAgB,EAAQ,YAAY,cACpC,EAAY,iBAAiB,CAAO,EAE1C,IAAK,GAAM,CAAE,QAAO,SAAQ,YAAY,EACtC,EAAO,oBAAoB,aAAa,CAAM,EAAG,aAAa,CAAK,CAAC,EAGtE,sBAAsB,CAAO,EAEzB,GAAgB,EAAW,QAAQ,IAAI,CAAY,GAAK,EAC1D,EAAc,aAAa,EAAQ,UAAW,EAAQ,WAAW,EAEjE,EAAQ,eAAe,aACrB,EAAQ,UACR,EAAQ,qBAAqB,aAAe,EAAQ,eAAiB,EAAQ,oBAAsB,IACrG,EAGF,EAAQ,YAAY,OAAO,EAC3B,EAAW,OAAS,KAEpB,EAAQ,OAAO,cAAc,EAAQ,UAAW,CAAK,EAErD,IAAM,EAAmG,CAAC,EAE1G,IAAK,GAAM,CAAC,EAAQ,KAAW,EAAQ,cAAe,CACpD,GAAI,CAAC,EAAW,QAAQ,IAAI,CAAM,EAAG,SAErC,IAAM,EAAa,EAAO,cAAc,EAEpC,gBAAgB,EAAQ,CAAU,GACpC,EAAQ,KAAK,CAAE,MAAO,EAAY,SAAQ,QAAO,CAAC,CAEtD,CAEA,GAAI,GAAgB,IAAiB,EAAQ,OAAQ,CACnD,IAAM,EAAe,EAAQ,KAAM,GAAW,EAAO,SAAW,EAAQ,MAAM,EACxE,EAAe,EAAQ,KAAM,GAAW,EAAO,SAAW,CAAY,EAE5E,GAAI,GAAgB,EAAc,CAChC,EAAW,WAAW,CACpB,OAAQ,EAAQ,UAChB,OAAQ,EAAQ,OAAO,QACvB,gBAAiB,EAAa,OAC9B,UAAW,EAAa,MACxB,OAAQ,EAAa,QACrB,gBAAiB,EAAa,OAC9B,UAAW,EAAa,KAC1B,CAAC,EAED,IAAM,EAAY,EAAa,MAAM,QAAQ,EAAQ,SAAS,EACxD,EAAY,EAAa,MAAM,OAIrC,EAAQ,OAAO,kBAAkB,CAAE,MAAO,EAAW,OAAQ,EAAQ,UAAW,MAAO,EAAW,KAAM,MAAO,CAAC,EAChH,EAAa,kBAAkB,CAAE,MAAO,EAAW,OAAQ,EAAQ,UAAW,MAAO,EAAW,KAAM,MAAO,CAAC,CAChH,CAEA,MACF,CAEA,IAAK,GAAM,CAAE,QAAO,SAAQ,YAAY,EACtC,EAAO,cAAc,CAAE,QAAO,SAAQ,KAAM,EAAQ,SAAU,CAAC,EAMjE,IAAM,EAAe,EAAQ,KAAM,GAAW,EAAO,SAAW,EAAQ,MAAM,EACxE,EAAa,EAAe,EAAa,MAAQ,EAAQ,OAAO,cAAc,EAC9E,EAAa,EAAW,QAAQ,EAAQ,SAAS,EAEvD,EAAQ,OAAO,kBAAkB,CAC/B,MAAO,EACP,OAAQ,EAAQ,UAChB,MAAO,EAAW,OAClB,KAAM,MACR,CAAC,CACH,CAEA,SAAS,cAAc,EAAgC,EAAkB,EAA4B,CACnG,GAAI,CAAC,EAAW,OAAQ,OAExB,GAAM,CAAE,UAAW,EAIjB,GACA,CAAC,EAAM,cACP,EAAM,aAAa,aAAe,QAClC,CAAC,EAAO,QACR,CAAC,EAAW,QAAQ,IAAI,EAAO,MAAM,GACrC,EAAO,OAAO,WAAW,GACzB,EAAO,OAAO,WAAW,EAEzB,cAAc,EAAY,CAAK,EAE/B,cAAc,EAAY,CAAK,CAEnC,CAEA,SAAS,gBACP,EACA,EACA,EACA,EACM,CACN,GAAI,CAAC,EAAS,OAEd,GAAM,CAAE,UAAW,EAAiB,cAAe,EAAW,QAAO,YAAa,EAC5E,EAAO,EAAU,sBAAsB,EAE7C,GAAI,IAAS,WAAY,CACnB,GAAmB,EAAM,QAAU,EAAK,IAAM,EAChD,EAAU,WAAa,EACd,GAAmB,EAAM,QAAU,EAAK,OAAS,IAC1D,EAAU,WAAa,GAGrB,GAAY,EAAM,QAAU,EAC9B,OAAO,SAAS,CAAE,KAAM,EAAG,IAAK,CAAC,CAAM,CAAC,EAC/B,GAAY,EAAM,QAAU,OAAO,YAAc,GAC1D,OAAO,SAAS,CAAE,KAAM,EAAG,IAAK,CAAM,CAAC,EAGzC,MACF,CAEI,GAAmB,EAAM,QAAU,EAAK,KAAO,EACjD,EAAU,YAAc,EACf,GAAmB,EAAM,QAAU,EAAK,MAAQ,IACzD,EAAU,YAAc,GAGtB,GAAY,EAAM,QAAU,EAC9B,OAAO,SAAS,CAAE,KAAM,CAAC,EAAO,IAAK,CAAE,CAAC,EAC/B,GAAY,EAAM,QAAU,OAAO,WAAa,GACzD,OAAO,SAAS,CAAE,KAAM,EAAO,IAAK,CAAE,CAAC,CAE3C,CAIA,SAAS,oBACP,EACA,EACA,EACA,EACe,CACf,IAAM,EAAe,EAAM,QAAQ,CAAI,EAEvC,GAAI,EAAe,EAAG,OAAO,KAE7B,IAAM,EAAa,IAAS,WAAa,YAAc,aACjD,EAAc,IAAS,WAAa,UAAY,YAChD,EAAY,EAAS,EAAO,CAAU,EACtC,EAAa,EAAS,EAAO,CAAW,EAC1C,EAEJ,GAAI,EAAW,EAAc,KAAK,IAAI,EAAM,OAAS,EAAG,EAAe,CAAC,OACnE,GAAI,EAAY,EAAc,KAAK,IAAI,EAAG,EAAe,CAAC,OAC1D,GAAI,EAAS,EAAO,MAAM,EAAG,EAAc,OAC3C,GAAI,EAAS,EAAO,KAAK,EAAG,EAAc,EAAM,OAAS,OACzD,OAAO,KAIZ,OAAO,IAAgB,EAAe,KAAO,CAC/C,CAmBA,SAAgB,oBAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,EAA4B,CAChC,OAAQ,KACR,WAAW,EAAa,CACtB,OACE,SACA,EAAQ,OACR,OAAO,OAAO,CACZ,GAAG,EACH,gBAAiB,aAAa,EAAM,eAAe,EACnD,UAAW,aAAa,EAAM,SAAS,EACvC,gBAAiB,aAAa,EAAM,eAAe,EACnD,UAAW,aAAa,EAAM,SAAS,CACzC,CAAC,CACH,CACF,EACA,YAAa,IAAI,IACjB,QAAS,IAAI,IACb,MAAO,IACT,EACM,EAAa,qBAAuB,CACxC,EAAM,OAAO,QAAQ,EAGrB,IAAK,IAAM,IAAa,CAAC,GAAG,EAAM,WAAW,EAC3C,EAAU,EAEZ,EAAM,YAAY,MAAM,EACxB,EAAM,QAAQ,MAAM,CACtB,CAAC,EAEK,EAAQ,CACZ,IAAI,gBAAiB,CACnB,OAAO,EAAW,cACpB,EACA,QAAS,EAAW,QACpB,IAAI,UAAW,CACb,OAAO,EAAW,QACpB,EACA,IAAI,YAAa,CACf,OAAO,EAAM,SAAW,IAC1B,GACC,GAAc,IACd,OAAO,SAAU,EAAW,OAAO,QACtC,EAiBA,OAfA,EAAoB,IAAI,EAAO,CAAK,EAEhC,EAAQ,QACV,EAAM,MAAQ,2BAA2B,EAAQ,QAAU,GAAO,CAAC,EAAI,EAAQ,MAAQ,GAAW,CAChG,GAAI,EAAM,OAAQ,OAAO,KACzB,IAAK,IAAM,KAAU,EAAM,QAAS,CAClC,IAAM,EAAa,EAAO,mBAAmB,CAAM,EAEnD,GAAI,EAAY,OAAO,CACzB,CAEA,OAAO,IACT,CAAC,GAGI,CACT,CA4BA,SAAgB,eAAe,EAAoC,CACjE,GAAM,CACJ,aAAa,GACb,OAAO,WACP,UACA,SACA,SACA,WAAW,GACX,mBAAmB,kBACnB,QAAQ,oBAAoB,GAC1B,EACJ,GAAI,IAAS,YAAc,IAAS,aAAc,MAAM,IAAI,SAAS,yCAAyC,EAC9G,IAAM,EAAiB,GAAQ,KAAK,GAAK,KAEzC,IAAI,IAAW,IAAA,IAAc,IAIlB,EACT,GAAI,CACF,EAAQ,cAAc,CAAc,CACtC,OAAS,EAAO,CACd,MAAM,IAAI,SAAS,6BAA6B,EAAO,GAAI,CAAE,MAAO,CAAM,CAAC,CAC7E,CAGF,IAAM,EAAoB,yBAAyB,CAAU,EACvD,EAAa,sBAAsB,CAAK,EAC9C,GAAI,CAAC,GAAG,EAAW,OAAO,CAAC,CAAC,KAAM,GAAe,EAAW,UAAY,CAAO,EAC7E,MAAM,IAAI,SAAS,0EAA0E,EAE/F,IAAM,cACH,EAAQ,MAAQ,EAAQ,MAAM,EAAK,MAAM,KAAK,EAAQ,QAAQ,EAAA,CAAqB,OACjF,GAAS,EAAK,gBAAkB,GAAW,EAAK,aAAa,CAAS,CACzE,EAEI,kBAAgC,SAAS,CAAC,CAAC,IAAK,GAAO,EAAO,CAAE,CAAC,EACjE,EAAkB,IAAI,IACtB,EAAwB,EAAQ,aAAa,MAAM,EAEnD,gBAAmB,GAAqD,CAC5E,IAAM,EAAW,EAAgB,IAAI,CAAc,EAEnD,GAAI,EAAU,OAAO,EAErB,IAAM,EAA6B,CACjC,cAAe,EAAe,aAAa,CAAW,EACtD,YAAa,EAAe,aAAa,CAAS,EAClD,UAAW,EAAe,aAAa,WAAW,EAClD,KAAM,EAAe,aAAa,MAAM,EACxC,SAAU,EAAe,aAAa,UAAU,EAChD,YAAa,EAAe,MAAM,WACpC,EAIA,OAFA,EAAgB,IAAI,EAAgB,CAAK,EAElC,CACT,EAEM,kBAAoB,EAA6B,EAAc,IAA+B,CAC9F,IAAU,KACZ,EAAe,gBAAgB,CAAI,EAEnC,EAAe,aAAa,EAAM,CAAK,CAE3C,EAEM,WAAa,EAAQ,SAAS,IAAY,CAC9C,EAAM,QAAS,GAAO,CACpB,IAAM,EAAY,gBAAgB,CAAE,EAEhC,EAAU,OAAS,MAAM,EAAG,aAAa,OAAQ,UAAU,EAE3D,EAAU,WAAa,OAAM,EAAG,SAAW,GAE3C,IAAW,IAAA,IASb,EAAG,aAAa,YAAa,MAAM,EAU/B,EAAW,QAAO,EAAG,MAAM,YAAc,SAlB7C,GACE,EAAG,iBAA8B,CAAc,CAAC,CAAC,QAAS,GAAa,CACrE,gBAAgB,CAAQ,EACxB,EAAS,aAAa,EAAa,EAAE,EACrC,EAAS,aAAa,YAAa,MAAM,EACrC,EAAW,QAAO,EAAS,MAAM,YAAc,OACrD,CAAC,CAcP,CAAC,CACH,EAEM,cAAwB,CAC5B,IAAM,EAAW,IAAI,IAIf,EAAW,EAAQ,MAAQ,EAAQ,MAAM,EAAK,MAAM,KAAK,EAAQ,QAAQ,EACzE,EAAa,EAAS,OAAQ,GAAS,aAAgB,aAAe,EAAK,gBAAkB,CAAO,EACpG,EAAuB,CAAC,EAE1B,EAAW,OAAW,EAAS,OAInC,IAAK,IAAM,KAAM,EACf,GAAI,CACF,IAAM,EAAM,EAAO,CAAE,EAErB,GAAI,EAAK,CACP,GAAI,EAAS,IAAI,CAAG,EAAG,CAEnB,GAAsC,EAAtC,EAEF,QACF,CAEA,EAAS,IAAI,CAAG,EAChB,EAAM,KAAK,CAAE,CACf,CACF,OAAS,EAAK,CAEV,GAAuG,OAAO,CAAG,EAAjH,CAEJ,CAGF,aAAa,EACb,IAAK,IAAM,KAAM,EACf,gBAAgB,CAAE,EAClB,EAAG,aAAa,EAAW,EAAE,EAE/B,UAAU,CAAK,CACjB,EAEM,iBAA2B,CAC/B,IAAK,GAAM,CAAC,EAAgB,KAAU,EACpC,iBAAiB,EAAgB,EAAa,EAAM,aAAa,EACjE,iBAAiB,EAAgB,EAAW,EAAM,WAAW,EAC7D,iBAAiB,EAAgB,YAAa,EAAM,SAAS,EAC7D,iBAAiB,EAAgB,OAAQ,EAAM,IAAI,EACnD,iBAAiB,EAAgB,WAAY,EAAM,QAAQ,EAC3D,EAAe,MAAM,YAAc,EAAM,YAG3C,EAAgB,MAAM,CACxB,EAEM,kBAAqB,GAAqC,CAC9D,IAAM,EAAI,SAAS,cAAc,KAAK,EAWtC,MATA,GAAE,UAAY,EACd,EAAE,aAAa,cAAe,MAAM,EAEhC,IAAS,aACX,EAAE,MAAM,MAAQ,GAAG,EAAO,YAAY,IAEtC,EAAE,MAAM,OAAS,GAAG,EAAO,aAAa,IAGnC,CACT,EAEM,EAA2B,CAC/B,cAAgB,GACd,OACE,YACA,EAAQ,UACR,OAAO,OAAO,CAAE,MAAO,aAAa,EAAM,KAAK,EAAG,OAAQ,aAAa,EAAM,MAAM,EAAG,KAAM,EAAM,IAAK,CAAC,CAC1G,EACF,UACA,cACA,gBAAiB,SACjB,eAAkB,gBAAgB,EAAQ,QAAQ,EAClD,qBAAsB,EAAM,IAAO,OAAO,kBAAmB,EAAQ,gBAAiB,EAAM,CAAE,EAC9F,eAAgB,EAAI,IAAU,OAAO,YAAa,EAAQ,UAAW,EAAI,CAAK,EAC9E,iBAAkB,EAAI,IAAU,OAAO,cAAe,EAAQ,YAAa,EAAI,CAAK,EACpF,kBAAoB,GAAU,OAAO,gBAAiB,EAAQ,cAAe,OAAO,OAAO,CAAK,CAAC,EACjG,mBAAqB,GAAW,CAC9B,GAAI,gBAAgB,EAAQ,QAAQ,GAAK,CAAC,EAAQ,SAAS,CAAM,EAAG,OAAO,KAE3E,IAAM,EAAO,EAAO,QAAqB,IAAI,EAAU,EAAE,EAEzD,GAAI,CAAC,GAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAI,EAAG,OAAO,KAEhD,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,GAAI,CAAC,EAAgB,OAAO,KAE5B,IAAM,EAAe,EAAO,QAAqB,CAAc,EAE/D,OAAO,GAAgB,EAAK,SAAS,CAAY,EAAI,EAAe,IACtE,CACF,EAEM,gBAAmB,GAAuB,CAG9C,GAFI,EAAW,QAEX,EAAQ,WAAW,EAAG,OAE1B,IAAM,EAAS,EAAE,OACX,EAAO,EAAO,QAAqB,IAAI,EAAU,EAAE,EAEzD,GAAI,CAAC,GAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAI,EAAG,OAEzC,GAAI,IAAW,IAAA,GAAW,CACxB,IAAM,EAAe,EAAiB,EAAO,QAAQ,CAAc,EAAI,KACvE,GAAI,CAAC,GAAgB,CAAC,EAAK,SAAS,CAAY,EAAG,MACrD,CAEA,IAAM,EAAiB,EAAK,cAE5B,GAAI,CAAC,EAAgB,OAErB,IAAM,EAAc,kBAAkB,CAAI,EACpC,EAAsB,EAAK,YAC3B,EAAW,EAAO,CAAI,EAGtB,EAAgB,IAAI,IAE1B,EAAc,IAAI,EAAS,EAAQ,cAAc,CAAC,EAClD,EAAK,aAAa,gBAAiB,EAAE,EACrC,EAAe,aAAa,EAAa,CAAmB,EAE5D,IAAM,EAAuB,CAC3B,UAAW,EACX,UAAW,EACX,UAAW,KACX,gBACA,gBAAiB,EAAK,MAAM,QAC5B,sBACA,iBACA,cACA,OAAQ,EACR,OAAQ,CACV,EAMA,IAJI,CAAC,iBAAiB,CAAC,GAAK,EAAE,oBAAmB,aAAa,CAAO,EAErE,EAAW,OAAS,EAEhB,EAAE,aACJ,GAAI,CAIF,GAHA,EAAE,aAAa,cAAgB,OAC/B,EAAE,aAAa,QAAQ,aAAc,CAAQ,EAEzC,EAAQ,UAAW,CACrB,IAAM,EACJ,OAAO,EAAQ,WAAc,WAAa,EAAQ,UAAU,EAAU,EAAM,CAAC,EAAI,EAAQ,UACrF,CAAC,EAAS,GAAW,EAAQ,iBAAmB,CAAC,EAAG,CAAC,EAEvD,GAAS,EAAE,aAAa,aAAa,EAAS,EAAS,CAAO,CACpE,CACF,OAAS,EAAO,CACT,GAA8B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAnF,CACP,CAGF,EAAQ,gBAAgB,EAAQ,UAAW,CAAC,EAC5C,EAAQ,kBAAkB,CACxB,MAAO,EAAQ,cAAc,CAAC,CAAC,QAAQ,CAAQ,EAC/C,OAAQ,EACR,MAAO,EAAQ,cAAc,CAAC,CAAC,OAC/B,KAAM,QACR,CAAC,CACH,EAEM,eAAkB,GAAuB,CAC7C,IAAM,EAAU,EAAW,OAI3B,GAFI,CAAC,GAED,EAAQ,OAAO,WAAW,GAAK,EAAQ,WAAW,EAAG,OAEzD,EAAE,eAAe,EACjB,gBAAgB,EAAG,EAAS,EAAM,CAAiB,EAGnD,cAAc,EAAS,CAAO,EAE9B,GAAM,CAAE,YAAW,eAAgB,EAC7B,EAAU,EAAE,OAAuB,QAAqB,IAAI,EAAU,EAAE,EAE9E,GAAI,GAAU,CAAC,SAAS,CAAC,CAAC,SAAS,CAAM,EAAG,OAE5C,GAAI,CAAC,EAAQ,CAIP,EAAY,gBAAkB,GAChC,EAAQ,YAAY,CAAW,EAGjC,EAAQ,OAAS,EAEjB,MACF,CAEA,GAAI,IAAW,GAAa,IAAW,EAAa,OAEpD,IAAM,EAAO,EAAO,sBAAsB,EACpC,EACJ,IAAS,WAAa,EAAE,SAAW,EAAK,IAAM,EAAK,OAAS,EAAI,EAAE,SAAW,EAAK,KAAO,EAAK,MAAQ,EAExG,EAAQ,aAAa,EAAa,EAAc,EAAO,YAAc,CAAM,EAC3E,EAAQ,OAAS,CACnB,EAEM,WAAc,GAAuB,CACzC,IAAM,EAAU,EAAW,OAEtB,IAED,EAAQ,OAAO,WAAW,GAAK,EAAQ,WAAW,IAEtD,EAAE,eAAe,EAGjB,EAAQ,OAAS,GACnB,EAEM,cAAiB,GAAuB,CACxC,EAAW,QAAQ,SAAW,GAElC,cAAc,EAAY,EAAG,EAAK,CACpC,EAEM,cAAiB,GAA2B,CAChD,GAAI,CAAC,GAAY,EAAQ,WAAW,EAAG,OAEvC,IAAM,EAAc,EAAE,OAChB,EAAO,GAAa,QAAqB,IAAI,EAAU,EAAE,EAE/D,GAAI,CAAC,GAAe,CAAC,GAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAI,EAAG,OAEzD,IAAM,EAAc,EAAY,QAC9B,4HACF,EACM,EAAiB,EAAiB,EAAY,QAAqB,CAAc,EAAI,KAE3F,GAAI,IAAgB,CAAC,GAAkB,CAAC,EAAK,SAAS,CAAc,GAAI,OAExE,IAAM,EAAQ,SAAS,EACjB,EAAY,EAAM,QAAQ,CAAI,EAC9B,EAAc,oBAAoB,EAAO,EAAM,EAAG,CAAI,EAG5D,GAAI,IAAgB,KAAM,OAE1B,IAAM,EAAa,EAAM,GACzB,GAAI,CAAC,EAAY,OAEjB,IAAM,EAAY,EAAM,IAAI,CAAM,EAC5B,EAAiB,CAAC,GAAG,CAAK,EAChC,EAAe,OAAO,EAAW,CAAC,EAClC,EAAe,OAAO,EAAa,EAAG,CAAI,EAC1C,IAAM,EAAW,EAAe,IAAI,CAAM,EACpC,EAAW,EAAO,CAAI,EAE5B,EAAE,eAAe,EACjB,EAAQ,oBAAoB,aAAa,CAAS,EAAG,aAAa,CAAQ,CAAC,EAC3E,EAAQ,aAAa,EAAM,EAAc,EAAY,EAAW,YAAc,CAAU,EACxF,EAAK,MAAM,EACX,EAAQ,cAAc,CAAE,MAAO,EAAU,OAAQ,EAAW,KAAM,CAAS,CAAC,EAC5E,EAAQ,kBAAkB,CACxB,MAAO,EACP,OAAQ,EACR,cAAe,EACf,MAAO,EAAS,OAChB,KAAM,MACR,CAAC,CACH,EAEA,UAAU,EAEV,IAAI,EACE,EAAa,qBAAuB,CACxC,EAAW,YAAY,OAAO,EAAW,OAAO,EAG9C,EAAW,SACV,EAAW,OAAO,SAAW,GAAW,EAAW,OAAO,SAAW,IACtE,CAAC,EAAW,OAAO,OAAO,GAE1B,cAAc,EAAY,IAAI,MAAM,SAAS,EAAgB,EAAI,EAGnE,IAAY,EACZ,EAAW,QAAQ,OAAO,CAAO,EACjC,iBAAiB,EAAS,OAAQ,CAAqB,EACvD,aAAa,CACf,CAAC,EAgBD,OAdA,EAAW,QAAQ,IAAI,CAAO,EAC9B,EAAY,EAAW,OAAO,SAAS,CAAO,EAE1C,IAA0B,MAAM,EAAQ,aAAa,OAAQ,MAAM,EAEvE,EAAQ,iBAAiB,YAAa,gBAAiB,CAAE,OAAQ,EAAW,cAAe,CAAC,EAC5F,EAAQ,iBAAiB,WAAY,eAAgB,CAAE,OAAQ,EAAW,cAAe,CAAC,EAC1F,EAAQ,iBAAiB,OAAQ,WAAY,CAAE,OAAQ,EAAW,cAAe,CAAC,EAClF,EAAQ,iBAAiB,UAAW,cAAe,CAAE,OAAQ,EAAW,cAAe,CAAC,EACxF,EAAQ,iBAAiB,UAAW,cAAe,CAAE,OAAQ,EAAW,cAAe,CAAC,EAGxF,EAAW,YAAY,IAAI,EAAW,OAAO,EAEtC,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAW,cACpB,EACA,QAAS,EAAW,QACpB,IAAI,UAAW,CACb,OAAO,EAAW,QACpB,EACA,IAAI,YAAa,CACf,OAAO,EAAW,QAAQ,SAAW,CACvC,GACC,OAAO,SAAU,EAAW,OAAO,SACpC,YAAe,CACb,UAAU,CACZ,CACF,CACF,CAQA,SAAgB,aAAgB,EAAqB,EAAwB,EAAkC,CAC7G,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAK,EAAO,CAAI,EACtB,GAAI,EAAK,IAAI,CAAE,EAAG,MAAM,IAAI,SAAS,wBAAwB,EAAG,EAAE,EAClE,EAAK,IAAI,EAAI,CAAI,CACnB,CACA,IAAM,EAAe,CAAC,EAEtB,IAAK,IAAM,KAAM,EAAK,CACpB,GAAI,CAAC,EAAK,IAAI,CAAE,EAAG,SAEnB,IAAM,EAAO,EAAK,IAAI,CAAE,EAExB,EAAQ,KAAK,CAAI,EACjB,EAAK,OAAO,CAAE,CAChB,CAEA,IAAK,IAAM,KAAQ,EAAK,OAAO,EAAG,EAAQ,KAAK,CAAI,EAEnD,OAAO,CACT"}
|
package/dist/drop-zone.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("./_dev.cjs");const e=require("./_shared.cjs"),t=require("./errors.cjs");function
|
|
1
|
+
require("./_dev.cjs");const e=require("./_shared.cjs"),t=require("./errors.cjs");function matchesAccept(e,t){return!t.length||t.some(t=>{let n=t.trim().toLowerCase(),r=e.type.toLowerCase();return n?n.startsWith(`.`)?e.name.toLowerCase().endsWith(n):n.endsWith(`/*`)?r.startsWith(n.slice(0,-1)):r===n:!1})}function itemsMatchAccept(e,t){return!t.length||Array.from(e).some(e=>e.kind===`file`&&t.some(t=>{let n=t.trim();return n.endsWith(`/*`)?e.type.startsWith(n.slice(0,-1)):n.startsWith(`.`)?!0:e.type===n}))}function applyFileFilters(e,t,n){let r=[],i=[];for(let n of e)(matchesAccept(n,t)?r:i).push(n);return n!==void 0&&r.length>n&&i.push(...r.splice(n)),{accepted:r,rejected:i}}function normalizeAccept(e){return e.map(e=>{let n=e.trim().toLowerCase(),r=/^\.[a-z0-9][a-z0-9._+-]*$/.test(n),i=/^[a-z0-9!#$&^_.+-]+\/(?:\*|[a-z0-9!#$&^_.+-]+)$/.test(n);if(!r&&!i)throw new t.DndError(`Invalid accept pattern: "${e}"`);return n})}function notify(e,t,...n){if(t)try{t(...n)}catch{`${e}`}}function createDropZone(n){let r=normalizeAccept(n.accept??[]);if(n.maxFiles!==void 0&&(!Number.isSafeInteger(n.maxFiles)||n.maxFiles<0))throw new t.DndError(`maxFiles must be a non-negative safe integer`);let{dropEffect:i=`copy`,element:a,maxFiles:o,onDrop:s,onDropRejected:c,onHoverChange:l,onValidatingChange:u}=n,d=0,f=!1,p=!1,m=new Set,setValidating=e=>{p!==e&&(p=e,notify(`onValidatingChange`,u,e))},updateCounter=e=>{let t=d>0&&f;d=Math.max(0,e),d===0&&(f=!1);let n=d>0&&f;n!==t&¬ify(`onHoverChange`,l,n)},resetCounter=()=>{updateCounter(0)},h=e.createDisposable(()=>{for(let e of m)e.abort();m.clear(),setValidating(!1),resetCounter()}),settle=(e,t)=>{e.length>0&¬ify(`onDrop`,s,[...e]),t.length>0&¬ify(`onDropRejected`,c,[...t])},settleForPaste=(e,t)=>{e.length>0&&(n.onPaste?notify(`onPaste`,n.onPaste,[...e]):notify(`onDrop`,s,[...e])),t.length>0&¬ify(`onDropRejected`,c,[...t])},dispatchWithValidation=(e,t)=>{let{accepted:i,rejected:a}=applyFileFilters(e,r,o),s=n.onValidate,c=s&&i.length>0?new AbortController:null;c&&(m.add(c),setValidating(!0));let finishValidation=()=>{c&&(m.delete(c),h.disposed||setValidating(m.size>0))},l;try{l=c&&s?s([...i],{signal:c.signal}):!0}catch(e){l=Promise.reject(e)}Promise.resolve(l).then(e=>{finishValidation(),!h.disposed&&(e===!0?t(i,a):t([],[...a,...i]))},()=>{finishValidation(),!h.disposed&&t([],[...a,...i])})},handleDragEnter=t=>{if(!e.resolveDisabled(n.disabled)){if(t.preventDefault(),d===0){let e=t.dataTransfer?.items;f=!r.length||!e?.length||itemsMatchAccept(e,r)}!f&&t.dataTransfer&&(t.dataTransfer.dropEffect=`none`),updateCounter(d+1)}},handleDragOver=t=>{e.resolveDisabled(n.disabled)||(t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect=f?i:`none`))},handleDragLeave=e=>{updateCounter(d-1)},handleDrop=t=>{if(resetCounter(),e.resolveDisabled(n.disabled))return;t.preventDefault();let r=t.dataTransfer?.files;r&&dispatchWithValidation(Array.from(r),settle)},handlePaste=t=>{if(e.resolveDisabled(n.disabled))return;let r=t.clipboardData?.files;r?.length&&(t.preventDefault(),dispatchWithValidation(Array.from(r),settleForPaste))};return a.addEventListener(`dragenter`,handleDragEnter,{signal:h.disposalSignal}),a.addEventListener(`dragover`,handleDragOver,{signal:h.disposalSignal}),a.addEventListener(`dragleave`,handleDragLeave,{signal:h.disposalSignal}),a.addEventListener(`drop`,handleDrop,{signal:h.disposalSignal}),n.paste&&a.addEventListener(`paste`,handlePaste,{signal:h.disposalSignal}),window.addEventListener(`dragend`,resetCounter,{signal:h.disposalSignal}),window.addEventListener(`drop`,resetCounter,{signal:h.disposalSignal}),{get disposalSignal(){return h.disposalSignal},dispose:h.dispose,get disposed(){return h.disposed},get hovered(){return d>0&&f},[Symbol.dispose]:h[Symbol.dispose],get validating(){return p}}}exports.createDropZone=createDropZone,exports.matchesAccept=matchesAccept;
|
|
2
2
|
//# sourceMappingURL=drop-zone.cjs.map
|
package/dist/drop-zone.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drop-zone.cjs","names":[],"sources":["../src/drop-zone.ts"],"sourcesContent":["import { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Accept matching ─────────────────────────────────────────────────────────\n\n/**\n * Test whether a `File` matches an accept pattern list.\n *\n * Each pattern can be:\n * - A MIME type: `'image/png'`\n * - A MIME wildcard: `'image/*'`\n * - A file extension: `'.pdf'`\n *\n * An empty list accepts everything.\n */\nexport function matchesAccept(file: File, accept: readonly string[]): boolean {\n if (!accept.length) return true;\n\n return accept.some((pattern) => {\n const p = pattern.trim().toLowerCase();\n const type = file.type.toLowerCase();\n\n if (!p) return false;\n if (p.startsWith('.')) return file.name.toLowerCase().endsWith(p);\n if (p.endsWith('/*')) return type.startsWith(p.slice(0, -1));\n\n return type === p;\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DropZoneOptions {\n /**\n * Accepted file types. Each entry may be:\n * - A MIME type: 'image/png'\n * - A MIME wildcard: 'image/*'\n * - A file extension: '.pdf'\n *\n * When empty the zone accepts everything.\n */\n accept?: readonly string[];\n /**\n * When `true`, all drag events are ignored and hover state does not change.\n *\n * Note: a disabled zone does not call `preventDefault` on drag or paste events,\n * so underlying elements (such as text editors) will still receive them.\n */\n disabled?: boolean;\n /**\n * The `dropEffect` to set on `dataTransfer` during `dragover`.\n * @default 'copy'\n */\n dropEffect?: DataTransfer['dropEffect'];\n /** The element to attach drag listeners to. */\n element: HTMLElement;\n /**\n * Maximum number of files accepted per drop. Files beyond this limit are\n * treated as rejected and forwarded to `onDropRejected`.\n *\n * When omitted there is no limit.\n */\n maxFiles?: number;\n /** Called when files are dropped or pasted (when `paste: true` and `onPaste` is omitted). Receives accepted files only. */\n onDrop?: (files: File[]) => void;\n /**\n * Called when dropped or pasted files are rejected by the `accept` filter, `maxFiles` limit, or `onValidate`.\n */\n onDropRejected?: (files: File[]) => void;\n /**\n * Called whenever hover state toggles.\n * Use this for drag-over styling.\n */\n onHoverChange?: (hovered: boolean) => void;\n /**\n * Called when files are pasted via the clipboard. Falls back to `onDrop` when omitted.\n * Only active when `paste: true`.\n */\n onPaste?: (files: File[]) => void;\n /**\n * Optional async file gating. Called after type/extension filtering, before `onDrop`.\n * Return (or resolve) `false` to move all type-accepted files to `onDropRejected`.\n *\n * Only receives type-accepted files (after `accept` and `maxFiles` filtering).\n * Files already rejected by the `accept` filter are forwarded to `onDropRejected`\n * unconditionally and are not passed to this function.\n *\n * While validation is in progress `zone.validating` is `true` and `onValidatingChange`\n * is called with `true`.\n *\n * @example\n * ```ts\n * onValidate: async (files, { signal }) => {\n * const ok = await checkServerQuota(files, { signal });\n * return ok;\n * }\n * ```\n */\n onValidate?: (files: File[], context: DropValidationContext) => boolean | Promise<boolean>;\n /**\n * Called whenever the async validation state changes.\n * Use this to drive loading spinners.\n *\n * @example\n * ```ts\n * onValidatingChange: (v) => { spinnerEl.hidden = !v; }\n * ```\n */\n onValidatingChange?: (validating: boolean) => void;\n /**\n * When `true`, a `paste` event listener is added to the drop-zone element. Pasted files run\n * through the same `accept`, `maxFiles`, and `onValidate` pipeline as dropped files.\n * @default false\n */\n paste?: boolean;\n}\n\nexport interface DropValidationContext {\n /** Aborts when the zone is disposed. Pass this to validation requests. */\n readonly signal: AbortSignal;\n}\n\nexport interface DropZone extends Disposable {\n /** Whether the pointer is currently dragging over the zone. */\n readonly hovered: boolean;\n /** `true` while an `onValidate` promise is pending. */\n readonly validating: boolean;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\n/**\n * DataTransferItem does not expose file names, so extension patterns (for example\n * `.pdf`) cannot be validated during drag-over. We treat these as a permissive\n * pre-check and perform exact filtering in `handleDrop` when `File` objects exist.\n */\nfunction itemsMatchAccept(items: DataTransferItemList, accept: string[]): boolean {\n if (!accept.length) return true;\n\n return Array.from(items).some(\n (item) =>\n item.kind === 'file' &&\n accept.some((pattern) => {\n const p = pattern.trim();\n\n if (p.endsWith('/*')) return item.type.startsWith(p.slice(0, -1));\n\n if (p.startsWith('.')) return true; // extension cannot be checked from DataTransferItem\n\n return item.type === p;\n }),\n );\n}\n\n/**\n * Filter `files` through `accept` patterns and `maxFiles` limit.\n * Returns accepted and rejected arrays.\n */\nfunction applyFileFilters(\n files: File[],\n accept: string[],\n maxFiles: number | undefined,\n): { accepted: File[]; rejected: File[] } {\n const accepted: File[] = [];\n const rejected: File[] = [];\n\n for (const f of files) {\n (matchesAccept(f, accept) ? accepted : rejected).push(f);\n }\n\n if (maxFiles !== undefined && accepted.length > maxFiles) {\n rejected.push(...accepted.splice(maxFiles));\n }\n\n return { accepted, rejected };\n}\n\n// ─── createDropZone ───────────────────────────────────────────────────────────\n\nfunction normalizeAccept(patterns: readonly string[]): string[] {\n return patterns.map((pattern) => {\n const normalized = pattern.trim().toLowerCase();\n const extension = /^\\.[a-z0-9][a-z0-9._+-]*$/.test(normalized);\n const mime = /^[a-z0-9!#$&^_.+-]+\\/(?:\\*|[a-z0-9!#$&^_.+-]+)$/.test(normalized);\n\n if (!extension && !mime) throw new DndError(`Invalid accept pattern: \"${pattern}\"`);\n return normalized;\n });\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n/**\n * Attach drag-and-drop behaviour to a DOM element.\n *\n * Accepts files and provides hover state management with configurable MIME/extension filtering.\n * Calls `onDrop` only for accepted files and `onDropRejected` for filtered files.\n *\n * @example\n * ```ts\n * import { createDropZone } from '@vielzeug/dnd';\n *\n * using zone = createDropZone({\n * element: dropZoneEl,\n * accept: ['image/*', '.pdf'],\n * maxFiles: 5,\n * onDrop: (files) => { console.log('Dropped:', files); },\n * onHoverChange: (hovered) => {\n * dropZoneEl.classList.toggle('drag-over', hovered);\n * },\n * });\n * ```\n */\nexport function createDropZone(options: DropZoneOptions): DropZone {\n const accept = normalizeAccept(options.accept ?? []);\n if (options.maxFiles !== undefined && (!Number.isSafeInteger(options.maxFiles) || options.maxFiles < 0)) {\n throw new DndError('maxFiles must be a non-negative safe integer');\n }\n\n const { dropEffect = 'copy', element, maxFiles, onDrop, onDropRejected, onHoverChange, onValidatingChange } = options;\n\n let dragCounter = 0;\n // Whether the *current* drag's payload passes the accept filter.\n // Determined on the first dragenter and held for the duration of the drag.\n let dragAccepted = false;\n let validating = false;\n const validationControllers = new Set<AbortController>();\n\n const setValidating = (next: boolean): void => {\n if (validating === next) return;\n\n validating = next;\n notify('onValidatingChange', onValidatingChange, next);\n };\n\n const updateCounter = (next: number): void => {\n const wasHovered = dragCounter > 0 && dragAccepted;\n\n dragCounter = Math.max(0, next);\n\n // Reset acceptance state when the drag fully leaves so the next drag starts clean.\n if (dragCounter === 0) dragAccepted = false;\n\n const hovered = dragCounter > 0 && dragAccepted;\n\n if (hovered !== wasHovered) notify('onHoverChange', onHoverChange, hovered);\n };\n\n const resetCounter = (): void => {\n updateCounter(0);\n };\n\n const disposable = createDisposable(() => {\n for (const controller of validationControllers) controller.abort();\n\n validationControllers.clear();\n setValidating(false);\n resetCounter();\n });\n\n // Settle the final accepted/rejected split and fire callbacks.\n const settle = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) notify('onDrop', onDrop, [...acceptedFiles]);\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Settle for paste events (which may use onPaste instead of onDrop).\n const settleForPaste = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) {\n if (options.onPaste) {\n notify('onPaste', options.onPaste, [...acceptedFiles]);\n } else {\n notify('onDrop', onDrop, [...acceptedFiles]);\n }\n }\n\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Run accept/maxFiles filter, then async onValidate, then settle.\n const dispatchWithValidation = (rawFiles: File[], settleFn: (accepted: File[], rejected: File[]) => void): void => {\n const { accepted, rejected: rej } = applyFileFilters(rawFiles, accept, maxFiles);\n const onValidate = options.onValidate;\n const validationController = onValidate && accepted.length > 0 ? new AbortController() : null;\n\n if (validationController) {\n validationControllers.add(validationController);\n setValidating(true);\n }\n\n const finishValidation = (): void => {\n if (!validationController) return;\n\n validationControllers.delete(validationController);\n\n if (!disposable.disposed) setValidating(validationControllers.size > 0);\n };\n\n let validation: boolean | Promise<boolean>;\n\n try {\n validation =\n validationController && onValidate ? onValidate([...accepted], { signal: validationController.signal }) : true;\n } catch (error) {\n validation = Promise.reject(error);\n }\n\n void Promise.resolve(validation).then(\n (valid) => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n if (valid === true) {\n settleFn(accepted, rej);\n } else {\n settleFn([], [...rej, ...accepted]);\n }\n },\n () => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n settleFn([], [...rej, ...accepted]);\n },\n );\n };\n\n const handleDragEnter = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n // Evaluate the filter once per drag (on first entry) — the payload is\n // constant for the lifetime of a drag operation.\n if (dragCounter === 0) {\n const items = e.dataTransfer?.items;\n\n dragAccepted = !accept.length || !items?.length || itemsMatchAccept(items, accept);\n }\n\n if (!dragAccepted && e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n\n // Always increment so every dragenter is paired with its dragleave,\n // regardless of acceptance. This prevents counter under-runs.\n updateCounter(dragCounter + 1);\n };\n\n const handleDragOver = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n if (e.dataTransfer) e.dataTransfer.dropEffect = dragAccepted ? dropEffect : 'none';\n };\n\n const handleDragLeave = (_e: DragEvent): void => {\n // Always decrement to balance the paired dragenter — disabling after enter\n // must not leave the counter permanently incremented.\n updateCounter(dragCounter - 1);\n };\n\n const handleDrop = (e: DragEvent): void => {\n // Reset counter first (idempotent at 0) so hover never sticks even when disabled.\n resetCounter();\n\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n const raw = e.dataTransfer?.files;\n\n if (!raw) return;\n\n dispatchWithValidation(Array.from(raw), settle);\n };\n\n const handlePaste = (e: ClipboardEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n const clipFiles = e.clipboardData?.files;\n\n if (!clipFiles?.length) return;\n\n e.preventDefault();\n dispatchWithValidation(Array.from(clipFiles), settleForPaste);\n };\n\n element.addEventListener('dragenter', handleDragEnter, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('dragleave', handleDragLeave, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n\n if (options.paste) element.addEventListener('paste', handlePaste, { signal: disposable.disposalSignal });\n\n // These global listeners catch drags that end outside the zone.\n // The window 'drop' also fires for in-zone drops, but resetCounter() is idempotent at counter=0.\n window.addEventListener('dragend', resetCounter, { signal: disposable.disposalSignal });\n window.addEventListener('drop', resetCounter, { signal: disposable.disposalSignal });\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get hovered() {\n return dragCounter > 0 && dragAccepted;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n get validating() {\n return validating;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drop-zone.cjs","names":[],"sources":["../src/drop-zone.ts"],"sourcesContent":["import { warn } from './_dev.js';\nimport { createDisposable, resolveDisabled } from './_shared.js';\nimport { DndError } from './errors.js';\nimport type { Disposable } from './types.js';\n\n// ─── Accept matching ─────────────────────────────────────────────────────────\n\n/**\n * Test whether a `File` matches an accept pattern list.\n *\n * Each pattern can be:\n * - A MIME type: `'image/png'`\n * - A MIME wildcard: `'image/*'`\n * - A file extension: `'.pdf'`\n *\n * An empty list accepts everything.\n */\nexport function matchesAccept(file: File, accept: readonly string[]): boolean {\n if (!accept.length) return true;\n\n return accept.some((pattern) => {\n const p = pattern.trim().toLowerCase();\n const type = file.type.toLowerCase();\n\n if (!p) return false;\n if (p.startsWith('.')) return file.name.toLowerCase().endsWith(p);\n if (p.endsWith('/*')) return type.startsWith(p.slice(0, -1));\n\n return type === p;\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DropZoneOptions {\n /**\n * Accepted file types. Each entry may be:\n * - A MIME type: 'image/png'\n * - A MIME wildcard: 'image/*'\n * - A file extension: '.pdf'\n *\n * When empty the zone accepts everything.\n */\n accept?: readonly string[];\n /**\n * When `true`, all drag events are ignored and hover state does not change.\n *\n * Note: a disabled zone does not call `preventDefault` on drag or paste events,\n * so underlying elements (such as text editors) will still receive them.\n */\n disabled?: boolean;\n /**\n * The `dropEffect` to set on `dataTransfer` during `dragover`.\n * @default 'copy'\n */\n dropEffect?: DataTransfer['dropEffect'];\n /** The element to attach drag listeners to. */\n element: HTMLElement;\n /**\n * Maximum number of files accepted per drop. Files beyond this limit are\n * treated as rejected and forwarded to `onDropRejected`.\n *\n * When omitted there is no limit.\n */\n maxFiles?: number;\n /** Called when files are dropped or pasted (when `paste: true` and `onPaste` is omitted). Receives accepted files only. */\n onDrop?: (files: File[]) => void;\n /**\n * Called when dropped or pasted files are rejected by the `accept` filter, `maxFiles` limit, or `onValidate`.\n */\n onDropRejected?: (files: File[]) => void;\n /**\n * Called whenever hover state toggles.\n * Use this for drag-over styling.\n */\n onHoverChange?: (hovered: boolean) => void;\n /**\n * Called when files are pasted via the clipboard. Falls back to `onDrop` when omitted.\n * Only active when `paste: true`.\n */\n onPaste?: (files: File[]) => void;\n /**\n * Optional async file gating. Called after type/extension filtering, before `onDrop`.\n * Return (or resolve) `false` to move all type-accepted files to `onDropRejected`.\n *\n * Only receives type-accepted files (after `accept` and `maxFiles` filtering).\n * Files already rejected by the `accept` filter are forwarded to `onDropRejected`\n * unconditionally and are not passed to this function.\n *\n * While validation is in progress `zone.validating` is `true` and `onValidatingChange`\n * is called with `true`.\n *\n * @example\n * ```ts\n * onValidate: async (files, { signal }) => {\n * const ok = await checkServerQuota(files, { signal });\n * return ok;\n * }\n * ```\n */\n onValidate?: (files: File[], context: DropValidationContext) => boolean | Promise<boolean>;\n /**\n * Called whenever the async validation state changes.\n * Use this to drive loading spinners.\n *\n * @example\n * ```ts\n * onValidatingChange: (v) => { spinnerEl.hidden = !v; }\n * ```\n */\n onValidatingChange?: (validating: boolean) => void;\n /**\n * When `true`, a `paste` event listener is added to the drop-zone element. Pasted files run\n * through the same `accept`, `maxFiles`, and `onValidate` pipeline as dropped files.\n * @default false\n */\n paste?: boolean;\n}\n\nexport interface DropValidationContext {\n /** Aborts when the zone is disposed. Pass this to validation requests. */\n readonly signal: AbortSignal;\n}\n\nexport interface DropZone extends Disposable {\n /** Whether the pointer is currently dragging over the zone. */\n readonly hovered: boolean;\n /** `true` while an `onValidate` promise is pending. */\n readonly validating: boolean;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\n/**\n * DataTransferItem does not expose file names, so extension patterns (for example\n * `.pdf`) cannot be validated during drag-over. We treat these as a permissive\n * pre-check and perform exact filtering in `handleDrop` when `File` objects exist.\n */\nfunction itemsMatchAccept(items: DataTransferItemList, accept: string[]): boolean {\n if (!accept.length) return true;\n\n return Array.from(items).some(\n (item) =>\n item.kind === 'file' &&\n accept.some((pattern) => {\n const p = pattern.trim();\n\n if (p.endsWith('/*')) return item.type.startsWith(p.slice(0, -1));\n\n if (p.startsWith('.')) return true; // extension cannot be checked from DataTransferItem\n\n return item.type === p;\n }),\n );\n}\n\n/**\n * Filter `files` through `accept` patterns and `maxFiles` limit.\n * Returns accepted and rejected arrays.\n */\nfunction applyFileFilters(\n files: File[],\n accept: string[],\n maxFiles: number | undefined,\n): { accepted: File[]; rejected: File[] } {\n const accepted: File[] = [];\n const rejected: File[] = [];\n\n for (const f of files) {\n (matchesAccept(f, accept) ? accepted : rejected).push(f);\n }\n\n if (maxFiles !== undefined && accepted.length > maxFiles) {\n rejected.push(...accepted.splice(maxFiles));\n }\n\n return { accepted, rejected };\n}\n\n// ─── createDropZone ───────────────────────────────────────────────────────────\n\nfunction normalizeAccept(patterns: readonly string[]): string[] {\n return patterns.map((pattern) => {\n const normalized = pattern.trim().toLowerCase();\n const extension = /^\\.[a-z0-9][a-z0-9._+-]*$/.test(normalized);\n const mime = /^[a-z0-9!#$&^_.+-]+\\/(?:\\*|[a-z0-9!#$&^_.+-]+)$/.test(normalized);\n\n if (!extension && !mime) throw new DndError(`Invalid accept pattern: \"${pattern}\"`);\n return normalized;\n });\n}\n\nfunction notify<Args extends unknown[]>(\n name: string,\n callback: ((...args: Args) => void) | undefined,\n ...args: Args\n): void {\n if (!callback) return;\n try {\n callback(...args);\n } catch {\n warn(`${name} callback failed; Dnd continued without interrupting internal state.`);\n }\n}\n\n/**\n * Attach drag-and-drop behaviour to a DOM element.\n *\n * Accepts files and provides hover state management with configurable MIME/extension filtering.\n * Calls `onDrop` only for accepted files and `onDropRejected` for filtered files.\n *\n * @example\n * ```ts\n * import { createDropZone } from '@vielzeug/dnd';\n *\n * using zone = createDropZone({\n * element: dropZoneEl,\n * accept: ['image/*', '.pdf'],\n * maxFiles: 5,\n * onDrop: (files) => { console.log('Dropped:', files); },\n * onHoverChange: (hovered) => {\n * dropZoneEl.classList.toggle('drag-over', hovered);\n * },\n * });\n * ```\n */\nexport function createDropZone(options: DropZoneOptions): DropZone {\n const accept = normalizeAccept(options.accept ?? []);\n if (options.maxFiles !== undefined && (!Number.isSafeInteger(options.maxFiles) || options.maxFiles < 0)) {\n throw new DndError('maxFiles must be a non-negative safe integer');\n }\n\n const { dropEffect = 'copy', element, maxFiles, onDrop, onDropRejected, onHoverChange, onValidatingChange } = options;\n\n let dragCounter = 0;\n // Whether the *current* drag's payload passes the accept filter.\n // Determined on the first dragenter and held for the duration of the drag.\n let dragAccepted = false;\n let validating = false;\n const validationControllers = new Set<AbortController>();\n\n const setValidating = (next: boolean): void => {\n if (validating === next) return;\n\n validating = next;\n notify('onValidatingChange', onValidatingChange, next);\n };\n\n const updateCounter = (next: number): void => {\n const wasHovered = dragCounter > 0 && dragAccepted;\n\n dragCounter = Math.max(0, next);\n\n // Reset acceptance state when the drag fully leaves so the next drag starts clean.\n if (dragCounter === 0) dragAccepted = false;\n\n const hovered = dragCounter > 0 && dragAccepted;\n\n if (hovered !== wasHovered) notify('onHoverChange', onHoverChange, hovered);\n };\n\n const resetCounter = (): void => {\n updateCounter(0);\n };\n\n const disposable = createDisposable(() => {\n for (const controller of validationControllers) controller.abort();\n\n validationControllers.clear();\n setValidating(false);\n resetCounter();\n });\n\n // Settle the final accepted/rejected split and fire callbacks.\n const settle = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) notify('onDrop', onDrop, [...acceptedFiles]);\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Settle for paste events (which may use onPaste instead of onDrop).\n const settleForPaste = (acceptedFiles: File[], rejectedFiles: File[]): void => {\n if (acceptedFiles.length > 0) {\n if (options.onPaste) {\n notify('onPaste', options.onPaste, [...acceptedFiles]);\n } else {\n notify('onDrop', onDrop, [...acceptedFiles]);\n }\n }\n\n if (rejectedFiles.length > 0) notify('onDropRejected', onDropRejected, [...rejectedFiles]);\n };\n\n // Run accept/maxFiles filter, then async onValidate, then settle.\n const dispatchWithValidation = (rawFiles: File[], settleFn: (accepted: File[], rejected: File[]) => void): void => {\n const { accepted, rejected: rej } = applyFileFilters(rawFiles, accept, maxFiles);\n const onValidate = options.onValidate;\n const validationController = onValidate && accepted.length > 0 ? new AbortController() : null;\n\n if (validationController) {\n validationControllers.add(validationController);\n setValidating(true);\n }\n\n const finishValidation = (): void => {\n if (!validationController) return;\n\n validationControllers.delete(validationController);\n\n if (!disposable.disposed) setValidating(validationControllers.size > 0);\n };\n\n let validation: boolean | Promise<boolean>;\n\n try {\n validation =\n validationController && onValidate ? onValidate([...accepted], { signal: validationController.signal }) : true;\n } catch (error) {\n validation = Promise.reject(error);\n }\n\n void Promise.resolve(validation).then(\n (valid) => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n if (valid === true) {\n settleFn(accepted, rej);\n } else {\n settleFn([], [...rej, ...accepted]);\n }\n },\n () => {\n finishValidation();\n\n if (disposable.disposed) return;\n\n settleFn([], [...rej, ...accepted]);\n },\n );\n };\n\n const handleDragEnter = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n // Evaluate the filter once per drag (on first entry) — the payload is\n // constant for the lifetime of a drag operation.\n if (dragCounter === 0) {\n const items = e.dataTransfer?.items;\n\n dragAccepted = !accept.length || !items?.length || itemsMatchAccept(items, accept);\n }\n\n if (!dragAccepted && e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n\n // Always increment so every dragenter is paired with its dragleave,\n // regardless of acceptance. This prevents counter under-runs.\n updateCounter(dragCounter + 1);\n };\n\n const handleDragOver = (e: DragEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n if (e.dataTransfer) e.dataTransfer.dropEffect = dragAccepted ? dropEffect : 'none';\n };\n\n const handleDragLeave = (_e: DragEvent): void => {\n // Always decrement to balance the paired dragenter — disabling after enter\n // must not leave the counter permanently incremented.\n updateCounter(dragCounter - 1);\n };\n\n const handleDrop = (e: DragEvent): void => {\n // Reset counter first (idempotent at 0) so hover never sticks even when disabled.\n resetCounter();\n\n if (resolveDisabled(options.disabled)) return;\n\n e.preventDefault();\n\n const raw = e.dataTransfer?.files;\n\n if (!raw) return;\n\n dispatchWithValidation(Array.from(raw), settle);\n };\n\n const handlePaste = (e: ClipboardEvent): void => {\n if (resolveDisabled(options.disabled)) return;\n\n const clipFiles = e.clipboardData?.files;\n\n if (!clipFiles?.length) return;\n\n e.preventDefault();\n dispatchWithValidation(Array.from(clipFiles), settleForPaste);\n };\n\n element.addEventListener('dragenter', handleDragEnter, { signal: disposable.disposalSignal });\n element.addEventListener('dragover', handleDragOver, { signal: disposable.disposalSignal });\n element.addEventListener('dragleave', handleDragLeave, { signal: disposable.disposalSignal });\n element.addEventListener('drop', handleDrop, { signal: disposable.disposalSignal });\n\n if (options.paste) element.addEventListener('paste', handlePaste, { signal: disposable.disposalSignal });\n\n // These global listeners catch drags that end outside the zone.\n // The window 'drop' also fires for in-zone drops, but resetCounter() is idempotent at counter=0.\n window.addEventListener('dragend', resetCounter, { signal: disposable.disposalSignal });\n window.addEventListener('drop', resetCounter, { signal: disposable.disposalSignal });\n\n return {\n get disposalSignal() {\n return disposable.disposalSignal;\n },\n dispose: disposable.dispose,\n get disposed() {\n return disposable.disposed;\n },\n get hovered() {\n return dragCounter > 0 && dragAccepted;\n },\n [Symbol.dispose]: disposable[Symbol.dispose],\n get validating() {\n return validating;\n },\n };\n}\n"],"mappings":"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"}
|