Начало работы
Установка
Введите в своем терминале:
# 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 с помощью своих атрибутов выбирает участников процесса (актеров):
- атрибут
itemпозволяет выбрать перемещаемый элемент. Если атрибут не задан, будет использоваться ближайший по дереву<effdnd-actor item='...'>с заданным атрибутомitem. Если задан атрибут со значением#(<effdnd-trigger item='#'>) или в DOM-дереве выше нет ни одного<effdnd-actor item='...'>, то перемещаться будет сами триггер; - атрибут
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, а результаты вызова используйте прослушивания событий:
import { useRef } from 'react';
import { useDnD } from 'effdnd';
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"), если активен его клон. Для клонирования элемента включите режим item-mode="cloned".
Элемент с ролью scope пассивен, когда внутри него перемещается item. Это состояние очерчивает границы области перемещения.
Элемент с ролью scope становится активным, если находится в пассивном состоянии и item пытается выйти за его пределы. Это состояние показывает пользователю, что курсор переместился за допустимые границы.
Элемент с ролью target пассивен, когда он находится внутри scope с активным или пассивным состоянием. Это состояние показывает, куда можно переместить item.
Элемент с ролью target становится активным, если находится в пассивном состоянии и курсор с item находится над ним. Это состояние показывает, что target готовит принять item.
Режимы взаимодействия ролей
Базовое поведение библиотеки является достаточно низкоуровневым, поэтому существует возможность уточнить вариант использования через режим каждой роли:
item-modeопределяет режимitem, может устанавливать следующие значения:item-mode="keep"- элементitemбудет сохранять свои нове координаты после перемещения;item-mode="clone"- для элементаitemбудет создан клон, который и будет перемещаться;
target-modeопределяет режимtargetпосле его выбора и может устанавливать следующие значения:target-mode="prepend"- элементitemбудет добавлен вtargetв качестве первого дочернего элемента;target-mode="append"- элементitemбудет добавлен вtargetв качестве последнего дочернего элемента;target-mode="remove"- элементitemбудет удален из DOM-дерева;
scope-modeопределяет режимscopeв ходе drag-and-drop и может устанавливать следующие значения:scope-mode="order-x"- элементscopeпозволяет переставлять элементыitemвдоль осиx;scope-mode="order-y"- элементscopeпозволяет переставлять элементыitemвдоль осиy.
Стили
Внешний вид <effdnd-actor item='...'> во время перемещения устанавливается триггером с помощью атрибутов. Также с помощью атрибутов можно задать параметры анимации, ось перемещения и минимальную дистанцию срабатывания.
/export interface ITriggerAttrs {
/**
* Item name
* @description
* If the value is "#" then it will use `effnd-trigger` as item
*/
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;
}По уомлчанию внутри пустого триггера будет использоваться SVG иконка перемещения, вид которой зависит от атрибута axis. Чтобы скрыть иконку используйте свой контент внутри effdnd-trigger.
Веб-компонент effdnd-actor уже содержит начальные стили для пассивного и активного состояний своих ролей, но также позволяет отменить их с помощью атрибута unstyled. При этом <effdnd-actor unstyled="target"> отменяет только встроенные стили target, <effdnd-actor unstyled="scope"> отменяет только встроенные стили scope, а <effdnd-actor unstyled> отменяет все встроенные стили.
/**
* 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;
/**
* Item mode
*/
'item-mode'?: 'keep' | 'clone';
/**
* Target mode
*/
'target-mode'?: 'append' | 'prepend' | 'remove';
/**
* Scope mode
*/
'scope-mode'?: 'order-x' | 'order-y';
/**
* Scope transition duration
*/
'scope-dur'?: number;
/**
* Scope transition delay
*/
'scope-del'?: number;
/**
* Scope transition timing-function
*/
'scope-tf'?: string;
}Атрибуты scope-dur, scope-del и scope-del для элемента с ролью scope устанавливают дефолтные значения CSS свойства transition.
Атрибут contents позволяет установить стиль display:contents;.
Вы также можете задать свои 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
Функция ообъявляет веб-компоненты effdnd-trigger и effdnd-actor, а также возвращает обработчики для подписки на кастомные события:
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 = () => {
/**
* 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
Веб-компонент триггера содержит несколько полезных методов и свойств:
/**
* DnD trigger element
*/
export interface ITriggerElement extends HTMLElement {
/**
* Is trigger active
*/
isActive: boolean;
/**
* 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 translate
*/
resetDnD(): void;
}