This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@thisux/sveltednd - A lightweight drag and drop library for Svelte 5. Uses TypeScript strict mode, Svelte 5 runes ($state), and a dual-API approach (HTML5 Drag-and-Drop + Pointer Events for touch support).
bun run dev # Dev server
bun run build # Production build (vite build + svelte-package + publint)
bun run package # Package library only
bun run check # Type-check (svelte-check)
bun run lint # Prettier check + ESLint
bun run format # Auto-format with Prettier
bun run test # Run all tests once (vitest --run)
bun run test:unit # Tests in watch mode
bun run test:unit -- --run src/lib/actions/draggable.spec.ts # Single test fileThe library exports three things from src/lib/index.ts: two Svelte actions (draggable, droppable) and one reactive state object (dndState).
How it works:
draggableaction attaches drag event listeners (both HTML5 and pointer events) to an element- When dragging starts, it updates the global
dndState(Svelte 5$staterune insrc/lib/stores/dnd.svelte.ts) and dispatches custom events - On desktop,
pointercancelfires when the browser takes over the pointer for HTML5 drag — anhtml5DragActiveflag prevents this from resetting state mid-drag droppableaction listens for dragenter/dragleave/dragover/drop events (HTML5 path) and a document-levelpointermove+getBoundingClientRect(pointer/touch path), updatesdndState.targetContainer, and fires callbacks- On touch devices,
pointerover/pointeroutdon't fire on elements beneath the finger, so hover detection uses coordinate-based bounds checking instead - Application handles reordering in the
onDropcallback
Key source files:
src/lib/actions/draggable.ts- Drag action with handle support and interactive element blockingsrc/lib/actions/droppable.ts- Drop action with nested element counter tracking (dragEnterCounter) and coordinate-based touch hover detection (wasOver + getBoundingClientRect)src/lib/stores/dnd.svelte.ts- Global reactive state (~11 lines)src/lib/types/index.ts- All TypeScript interfaces (DragDropState<T>,DraggableOptions<T>, etc.)src/lib/styles/dnd.css- Default CSS classes (dragging,drag-over)src/routes/- Demo pages (Kanban, sortable list, grid, nested containers, etc.)
- Tabs for indentation, single quotes, no trailing commas, 100 char print width
- Always use
.jsextensions in imports (TypeScript NodeNext resolution) - Use
$libalias:import { x } from '$lib/actions/index.js' - Use
import typefor type-only imports - Svelte actions follow the pattern: setup, event handlers, addEventListener, return
{ update(), destroy() } - State uses Svelte 5
$staterunes, not legacy stores - Files: kebab-case. Functions: camelCase. Types: PascalCase. Constants: UPPER_SNAKE_CASE
- Test files use
*.spec.tspattern, colocated with source - Use feature branches, never commit to main directly
- All commits must follow Conventional Commits — enforced by Husky + commitlint
Format: type(scope): description
| Type | When to use |
|---|---|
feat |
New user-facing feature |
fix |
Bug fix |
docs |
Documentation only |
chore |
Tooling, deps, config |
refactor |
Code restructure, no behavior change |
test |
Adding or fixing tests |
ci |
CI/CD changes |
style |
Formatting only |
Examples: feat: add keyboard drag support, fix: prevent stuck drag state on mobile
The commit-msg hook will reject commits that don't follow this format.