svelte-dnd-action

repository·master·Indexed 24 days ago

https://github.com/isaachagoel/svelte-dnd-action

A drag-and-drop library for Svelte 3 and 4 that uses Svelte actions instead of higher-order components. It provides rich animations, nested containers, touch support, and keyboard accessibility. Key features include the dndzone action for container management, drag handles via dragHandleZone and dragHandle, and integration with Svelte's flip animation.

Tokens
4.4K
Snippets
9
Records
22
Agent score
83%

What's inside svelte-dnd-action

  1. Optimize Nested Zones (Experimental)

    master

    When using deeply nested zones, you can optimize performance by helping the library identify placeholder (shadow) items. Use the SHADOW_ITEM_MARKER_PROPERTY_NAME property on your items and pass it to a data-is-dnd-shadow-item-hint attribute.

    Note: When using this hint, you must include it in the key provided to your {#each} block to ensure the key remains unique when a shadow item is present.

    <script>
        import {dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME} from 'svelte-dnd-action';
        let items = [];
    </script>
    
    <section>
        <div use:dndzone={{items}} on:consider={e => items = e.detail.items} on:finalize={e => items = e.detail.items}>
            {#each items as item (item.id)}
                <div data-is-dnd-shadow-item-hint={item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}>
                    <h1>{item.title}</h1>
                </div>
            {/each}
        </div>
    </section>
  2. Basic Usage of dndzone

    master

    To implement drag and drop, use the dndzone action on a container element. You must provide an items array where every object has a unique id. You must also listen to the consider and finalize events to update your local state with the new items list provided in e.detail.items.

    Important Rules:

    • Every draggable item must have an id property.
    • Use the id as the key in your {#each} block.
    • The items array passed to the action must be the same one used in your {#each} block.
    • You must handle both consider and finalize events to keep the UI in sync.
    <script>
        import {flip} from "svelte/animate";
        import {dndzone} from "svelte-dnd-action";
        let items = [
            {id: 1, name: "item1"},
            {id: 2, name: "item2"},
            {id: 3, name: "item3"},
            {id: 4, name: "item4"}
        ];
        const flipDurationMs = 300;
    
        function handleDndConsider(e) {
            items = e.detail.items;
        }
        function handleDndFinalize(e) {
            items = e.detail.items;
        }
    </script>
    
    <style>
        section {
            width: 50%;
            padding: 0.3em;
            border: 1px solid black;
            overflow: scroll;
            height: 200px;
        }
        div {
            width: 50%;
            padding: 0.2em;
            border: 1px solid blue;
            margin: 0.15em 0;
        }
    </style>
    
    <section use:dndzone="{{items, flipDurationMs}}" on:consider="{handleDndConsider}" on:finalize="{handleDndFinalize}">
        {#each items as item(item.id)}
        <div animate:flip="{{duration: flipDurationMs}}">{item.name}</div>
        {/each}
    </section>
  3. Configure TypeScript for Svelte 4

    master

    If you are using Svelte 4 and need to fix type issues with custom events, add the following to your global.d.ts file:

    import { Item, DndEvent } from 'svelte-dnd-action';
    
    declare namespace svelteHTML {
        interface HTMLAttributes<T> {
            "on:consider"?: (event: CustomEvent<DndEvent<Item>> & {target: EventTarget & T}) => void;
            "on:finalize"?: (event: CustomEvent<DndEvent<Item>> & {target: EventTarget & T}) => void;
        }
    }
    declare type Item = import("svelte-dnd-action").Item;
    declare type DndEvent<ItemType = Item> = import("svelte-dnd-action").DndEvent<ItemType>;
    declare namespace svelteHTML {
        interface HTMLAttributes<T> {
            "on:consider"?: (event: CustomEvent<DndEvent<ItemType>> & {target: EventTarget & T}) => void;
            "on:finalize"?: (event: CustomEvent<DndEvent<ItemType>> & {target: EventTarget & T}) => void;
        }
    }
  4. Configure Accessibility for Screen Readers

    master

    To provide a high-quality experience for screen reader users, add aria-label attributes to both the container and every draggable item. The library will automatically manage ARIA attributes and instructions based on these labels.

    Keyboard Support Features:

    • Tab into a container to hear instructions.
    • Space/Enter on an item to enter dragging mode.
    • Arrow keys to move the item position.
    • Tab to another container to move the item there.
    • Space/Enter or Escape to exit dragging mode.
    <h2 id="list-title">{listName}</h2>
    <section 
        aria-label="{listName}" 
        use:dndzone="{{items, flipDurationMs}}" 
        on:consider="{handleDndConsider}" 
        on:finalize="{handleDndFinalize}"
    >
        {#each items as item(item.id)}
        <div aria-label="{item.name}" animate:flip="{{duration: flipDurationMs}}">
            {item.name}
        </div>
        {/each}
    </section>
  5. Implement Drag Handles

    master

    To use specific elements as drag handles instead of the entire item being draggable, use the dragHandleZone wrapper action on the container and the dragHandle action on the handle element itself.

    Note: The handle must be inside the bounding rect of the draggable item. Always include an aria-label on the handle for accessibility.

    <script>
        import {dragHandleZone, dragHandle} from "svelte-dnd-action";
        import {flip} from "svelte/animate";
    
        let items = [
            { id: 1, text: "Item 1" },
            { id: 2, text: "Item 2" }
        ];
        const flipDurationMs = 100;
    
        function handleSort(e) {
            items = e.detail.items;
        }
    </script>
    
    <section use:dragHandleZone="{{ items, flipDurationMs }}" on:consider="{handleSort}" on:finalize="{handleSort}">
        {#each items as item (item.id)}
        <div animate:flip="{{ duration: flipDurationMs }}">
            <div use:dragHandle aria-label="drag-handle for {item.text}" class="handle" />
            <span>{item.text}</span>
        </div>
        {/each}
    </section>
  6. Use dragHandleZone and dragHandle for drag handles

    master

    To implement drag-and-drop using specific handles instead of the entire item, you must use two complementary actions: dragHandleZone on the container and dragHandle on the specific element within each item that acts as the handle.

    1. dragHandleZone(node, options): A wrapper around the standard dndzone action. It manages the internal state required to enable/disable dragging based on whether a handle is being interacted with. All options passed to dragHandleZone are passed down to the underlying dndzone.
    2. dragHandle(handle): An action applied to the element intended to be the handle. It automatically sets role="button" and manages keyboard accessibility (Enter/Space) and pointer interactions (mousedown/touchstart). It also manages the visual cursor state (grab vs grabbing) and tabIndex based on the zone's state.

    Note: When using dragHandleZone, the dragHandle action is required on elements inside the zone to function correctly.

  7. Override the item ID key name

    master

    If your data uses a key other than id (e.g., _id for PouchDB), you can globally override the identifier key. This must be called before any dndzone is rendered, ideally in your top-level App component.

    import {overrideItemIdKeyNameBeforeInitialisingDndZones} from "svelte-dnd-action";
    overrideItemIdKeyNameBeforeInitialisingDndZones("_id");
  8. Handle dndzone events

    master

    The dndzone action dispatches two custom events:

    1. consider: Dispatched when a dragged element moves to a new position or leaves a zone. Use this to update the UI immediately.
    2. finalize: Dispatched when the element is dropped. Use this for permanent state updates (e.g., saving to a server).

    Both events provide e.detail containing:

    • items: The updated items list.
    • info: An object for advanced logic containing:
      • trigger: One of DRAG_STARTED, DRAGGED_ENTERED, DRAGGED_ENTERED_ANOTHER, DRAGGED_OVER_INDEX, DRAGGED_LEFT, DRAGGED_LEFT_ALL, DROPPED_INTO_ZONE, DROPPED_INTO_ANOTHER, DROPPED_OUTSIDE_OF_ANY, DRAG_STOPPED.
      • id: The ID of the dragged item.
      • source: Either POINTER or KEYBOARD.
  9. Set Feature Flags

    master

    You can control global optional behavior using setFeatureFlag.

    Currently available flag:

    • USE_COMPUTED_STYLE_INSTEAD_OF_BOUNDING_RECT (defaults to false)
    import {setFeatureFlag, FEATURE_FLAG_NAMES} from "svelte-dnd-action";
    setFeatureFlag(FEATURE_FLAG_NAMES.USE_COMPUTED_STYLE_INSTEAD_OF_BOUNDING_RECT, true);
  10. Configure dndzone options

    master

    The dndzone action accepts an options object to customize behavior.

    NameTypeDefaultDescription
    itemsArray<Object>RequiredThe data array used for the list. Each object must have a unique id property.
    flipDurationMsNumber0Duration for Svelte's flip animation. Defaults to 100ms if unset.
    typeStringInternalZones with the same type can exchange elements.
    dragDisabledBooleanfalseIf true, elements cannot be dragged out of the zone.
    morphDisabledBooleanfalseIf true, prevents the dragged element from morphing to its new position during hover.
    dropFromOthersDisabledBooleanfalseIf true, prevents dropping elements from other zones of the same type.
    zoneTabIndexNumber0Custom tabindex for the container.
    zoneItemTabIndexNumber0Custom tabindex for items.
    dropTargetStyleObject{outline: '...'}Inline styles applied to the zone when it is a valid drop target.
    dropTargetClassesArray<String>[]Global classes applied to the zone when it is a valid drop target.
    transformDraggedElementFunction() => {}(element, data, index) => {}. Invoked when hovering over a new index. Allows overriding properties on the dragged element.
    autoAriaDisabledBooleanfalseDisables automatic ARIA attributes. Use if implementing custom accessibility.
    centreDraggedOnCursorBooleanfalsePositions the center of the dragged element on the cursor.
    useCursorForDetectionBooleanfalseUses cursor position instead of element center for drop detection.
    dropAnimationDisabledBooleanfalseDisables the animation of the dropped element to its final place.
    delayTouchStartBoolean/NumberfalsePrevents accidental drags on touch. Use true (80ms default) or a specific millisecond value.