Skip to content

Getting started

Installation

Enter in your terminal:

sh
# npm
npm i effdnd

# pnpm
pnpm add effdnd

# yarn
yarn add effdnd

Quick start

In short, effdnd uses two custom web component:

  • effdnd trigger is triggerring drag-and-drop,
  • effdnd-actor indicates the areas of the layout that will participate in the drag-and-drop process (play their roles).

The web component effdnd-actor can "play" several roles:

  • item - the item being moved.,
  • target - the target of the move,
  • scope - movement boundaries.

To set a role, you need to set the attribute of the same name on the effdnd-actor element. Multiple roles can be set for a single element. The attribute values will be used to identify drag-and-drop participants by the effdnd-trigger component.

At the beginning of drag-and-drop, the effdnd-trigger web component uses its attributes to select the participants in the process (actors):

  • the item attribute allows you to select the item to move. If the attribute is not specified, the closest in the tree <effdnd-actor item='...'> with the specified attribute item will be used. If an attribute with the value # is set (<effdnd-trigger item='#'>) or there is no <effdnd-actor item='...'> in the DOM tree above, then the trigger itself will move. If the trigger has the clone attribute, a clone of the HTML element will be created for the movement;
  • the scope attribute allows you to select the available area of movement. If the attribute is not specified, the closest in the tree <effdnd-actor scope='...'> with the specified scope attribute will be used. If an attribute with the value # is set (<effdnd-trigger scope='#'>) or there is no <effdnd-actor scope='...'> in the DOM tree above, then the scope area will be document.body;
  • the target attribute allows you to select the available movement targets. Movement targets generate special events when drag-and-drop ends on them. All <effdnd-actor target='...'> elements located inside the scope area will be considered as movement targets, the names of which begin with the value of the target attribute of the trigger. This means that if the attribute is not specified, then all <effdnd-actor target='...'> are available inside the <effdnd-actor scope='...'>.

To define both web components, simply call the useDnD function, and use the results of the call to create an event listeners

jsx
import { useDnD } from 'effdnd';

// you can pass style parameters to useDnD,
// to override the movement parameters
// (for more information, see the type `TUseDnD`)
const { observe } = useDnD();

export const Component = () => {
    const ref = useRef();
    useEffect(() => {
      // you can subscribe to Drag-and-Drop events
      const unobserve = observe((e) => {
        // the event is being processed here
      }, ref.current);
      // and you can unsubscribe
      return () => unobserve();
    });
    return <effdnd-actor scope='top' ref={ref} >
        <div className="targets-wrapper">
            <effdnd-actor target='target-1'>...</effdnd-actor>
            <effdnd-actor target='target-2'>...</effdnd-actor>
        </div>
        <div id="items-wrapper">
            <effdnd-actor item='item-1'>
                <effdnd-trigger>Trigger #1</effdnd-trigger>
            </effdnd-actor>
            <effdnd-actor item='item-2'>
                <effdnd-trigger>Trigger #2</effdnd-trigger>
            </effdnd-actor>
        </div>
    </effdnd-actor>;
}

Active and passive elements

During drag-and-drop, the effdnd-trigger sets the dynamic state attribute on the selected effdnd-actor elements. That is, at the very beginning, the trigger determines what is being moved (item), how is this limited (scope) and where it should be moved to (target). The elements can be

  • without a state,
  • in passive state (state="passive"),
  • in active state (state="active").

The element with the item role is active while it is being moved. In addition, an element with the role item can be in the cloned state (state="cloned") if its clone is active.

The element with the scope role is passive when the item moves inside it.

The element with the scope role becomes active if it is in a passive state and the item tries to go beyond it. This state shows the user the available movement limits.

The element with the target role is passive when it is inside a scope with an active or passive state.

The item with the target role becomes active if it is in a passive state and the item moves over it.

Component styles

The appearance of <effdnd-actor item='...'> is set by the trigger using attributes during movement. You can also use attributes to set animation parameters, the axis of movement, and the minimum firing distance.

ts
/**
 * DnD trigger attributes
 */
export interface ITriggerAttrs {
    /**
     * Item name
     * @description
     * If the value is "#" then it will use `effnd-trigger` as scope
     */
    item?: string;
    /**
     * Bounding scope name
     * @description
     * If the value is "#" then it will use `document.body` as scope
     */
    scope?: string;
    /**
     * Target name or its prefix
     */
    target?: string;
    /**
     * Triggering distance
     */
    dist?: string;
    /**
     * DnD axis
     */
    axis?: string;
    /**
     * Triggering event
     * @description
     * Both 'touch' and 'mouse' by default
     */
    event?: 'touch' | 'mouse';
    /**
     * Transition duration
     */
    dur?: string;
    /**
     * Transition delay
     */
    del?: string;
    /**
     * Transition timing-function
     */
    tf?: string;
    /**
     * Item z-index in active state
     */
    zi?: string;
    /**
     * Item opacity in active state
     */
    opacity?: string;
    /**
     * Use item clone while dragging
     */
    clone?: boolean;
}

The web component effdnd-actor already contains the initial styles for the passive and active states of both target and scope, but it also allows you to cancel them using the unstyled attribute. At the same time, <effdnd-actor unstyled="target"> cancels only the built-in styles of target, <effdnd-actor unstyled="scope"> cancels only the built-in styles of scope, and <effdnd-actor unstyled> cancels all built-in styles.

ts
/**
 * DnD actor attributes
 */
export interface IActorAttrs {
    /**
     * Item name
     */
    item?: string;
    /**
     * Scope name
     */
    scope?: string;
    /**
     * Target name
     */
    target?: string;
    /**
     * Actor state
     */
    state?: 'active' | 'passive';
    /**
     * Reset target state styles
     */
    unstyled?: boolean | 'target' | 'scope';
    /**
     * Display contents
     */
    contents?: boolean;
}

The contents attribute allows you to set the display:contents; style. If you add the unstyled attribute, you can set your own CSS styles for different states.

css
effdnd-actor[target][state=passive] .custom-dnd-target {
  outline: 4px solid #646cffaa;
}
effdnd-actor[target][state=active] .custom-dnd-target {
  outline: 4px solid #646cffaa;
  background: #646cffaa;
}

useDnD

The function accepts transition settings as the first argument and custom styles as the second. It returns functions for listening to custom events:

ts
export type TEventDetail = {
    type: TEventType;
    refs: TRefs;
    keys: TKeys;
    event: MouseEvent | TouchEvent;
};

interface TDnDEvent extends CustomEvent {
    detail: TEventDetail;
}

export type TDnDCallback = (event: TDnDEvent) => void;

export type TUseDnD = (defs?: Partial<{
    /**
     * Duration
     */
    dur: number;
    /**
     * Delay
     */
    del: number;
    /**
     * Transition-timing-function
     */
    tf: string;
    /**
     * Active DnD item z-index
     */
    zi: number;
    /**
     * Active DnD item opacity
     */
    opacity: number;
}>) => {
    /**
     * Observe DnD events
     * @param callback - event handler
     * @param element - HTML element
     */
    observe: (callback: TDnDCallback, element?: HTMLElement) => () => void;
    /**
     * Unobserve DnD events
     * @param callback - event handler
     * @param element - HTML element
     */
    unobserve: (callback: TDnDCallback, element?: HTMLElement) => void;
};

effdnd trigger

The trigger web component contains several useful methods and properties.

ts
/**
 * DnD trigger element 
 */
export interface ITriggerElement extends HTMLElement {
    /**
     * DnD item y-offset
     */
    dndY: number;
    /**
     * DnD item x-offset
     */
    dndX: number;
    /**
     * DnD item
     */
    dndItem: HTMLElement;
    /**
     * DnD scope
     */
    dndScope: HTMLElement;
    /**
     * DnD targets
     */
    dndTargets: Set<HTMLElement>;
    /**
     * Reset DnD transforms
     */
    resetDnD(options?: KeyframeAnimationOptions): Promise<void>;
}

Released under the Apache-2.0 License.