Skip to content

Начало работы

Установка

Введите в своем терминале:

sh
# npm
npm i effdnd

# pnpm
pnpm add effdnd

# yarn
yarn add effdnd

Быстрый старт

Вкратце, effdnd использует два пользовательских веб-компонента:

  • effdnd-trigger инициирует drag-and-drop,
  • effdnd-actor обозначает области верстки, которые будут участвовать в процессе drag-and-drop (играть свои роли).

Веб компонент effdnd-actor может "играть" несколько ролей:

  • item - перемещаемый элемент,
  • target - цель перемещения,
  • scope - границы перемещения.

Для задания роли нужно установить одноименнный атрибут на элемент effdnd-actor. На один элемент можно установить несколько ролей. Значение атрибутов можно будет использовать для определения участников drag-and-drop через компонент effdnd-trigger.

В начале drag-and-drop веб-компонент effdnd-trigger с помощью своих атрибутов выбирает участников процесса (актеров):

  • атрибут item позволяет выбрать перемещаемый элемент. Если атрибут не задан, будет использоваться ближайший по дереву <effdnd-actor item='...'> с заданным атрибутом item. Если задан атрибут со значением # (<effdnd-trigger item='#'>) или в DOM-дереве выше нет ни одного <effdnd-actor item='...'>, то перемещаться будет сами триггер. Если триггер имеет атрибут clone, то для перемещения будет создан клон HTML-элемента;
  • атрибут scope позволяет выбрать доступную область перемещения. Если атрибут не задан, будет использоваться ближайший по дереву <effdnd-actor scope='...'> с заданным атрибутом scope. Если задан атрибут со значением # (<effdnd-trigger scope='#'>) или в DOM-дереве выше нет ни одного <effdnd-actor scope='...'>, то областью перемещения будет считаться document.body;
  • атрибут target позволяет выбрать доступные цели перемещения. Цели перемещения генерируют специальные события, когда drag-and-drop заканчивается над ними. Целями перемещения будут считаться все находящиеся внутри области scope элементы <effdnd-actor target='...'>, названия которых начинаются со значения атрибута target у триггера. Это значит, что если атрибут не задан, то доступны все <effdnd-actor target='...'> внутри текщуго <effdnd-actor scope='...'>.

Чтобы определить оба веб-компонента, просто вызовите функицю useDnD, а результаты вызова используйте для создания прослушивателя событий.

jsx
import { useDnD } from 'effdnd';

// вы можете передать параметры стиля в useDnD,
// чтобы переопределить параметры перемещения
// (подробнее смотрите тип `TUseDnD`)
const { observe } = useDnD();

export const Component = () => {
    const ref = useRef();
    useEffect(() => {
      // вы можете подписаться на Drag-and-Drop события
      const unobserve = observe((e) => {
        // здесь идет обработка события
      }, ref.current);
      // и вы можете отписаться
      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>;
}

Активные и пассивные элементы

Во время drag-and-drop effdnd-trigger устанавливает динамический атрибут state на выбранных актеров effdnd-actor. То есть в самом начале триггер определяет, что перемещается (item), где может перемещеаться (scope) и куда следует перемещать (target). Элементы могут быть

  • без состояния,
  • в пассивном состоянии (state="passive"),
  • в активном состоянии (state="active").

Элемент с ролью item активен во время его перемещения. Кроме того элемент с ролью item может быть в состоянии клонирован (state="cloned"), если активен его клон.

Элемент с ролью scope пассивен, когда внутри него перемещается item.

Элемент с ролью scope становится активным, если находится в пассивном состоянии и item пытается выйти за его пределы. Это состояние показывает пользователю доступные пределы перемещения.

Элемент с ролью target пассивен, когда он находится внутри scope с активным или пассивным состоянием.

Элемент с ролью target становится активным, если находится в пассивном состоянии и item перемещается над ним.

Стили компонентов

Внешний вид <effdnd-actor item='...'> во время перемещения устанавливается триггером с помощью атрибутов. Также с помощью атрибутов можно задать параметры анимации, ось перемещения и минимальную дистанцию срабатывания.

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;
}

Веб-компонент effdnd-actor уже содержит начальные стили для пассивного и активного состояний как target, так и scope, но также позволяет отменить их с помощью атрибута unstyled. При этом <effdnd-actor unstyled="target"> отменяет только встроенные стили target, <effdnd-actor unstyled="scope"> отменяет только встроенные стили scope, а <effdnd-actor unstyled> отменяет все встроенные стили.

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;
}

Атрибут contents позволяет вообще установить стиль display:contents;. Если при этом добавить атрибут unstyled, то можно задать свои CSS-стили для различных состояний.

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

Функция принимает настройки перехода в качестве первого аргумента и пользовательские стили в качестве второго. Она возвращает функции для прослушивания кастомных событий:

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

Веб-компонент триггера содержит несколько полезных методов и свойств.

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>;
}

Опубликовано под лицензией Apache License 2.0