Начало работы
Установка
Введите в своем терминале:
# 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, а результаты вызова используйте для создания прослушивателя событий.
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='...'> во время перемещения устанавливается триггером с помощью атрибутов. Также с помощью атрибутов можно задать параметры анимации, ось перемещения и минимальную дистанцию срабатывания.
/**
* 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> отменяет все встроенные стили.
/**
* 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-стили для различных состояний.
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
Функция принимает настройки перехода в качестве первого аргумента и пользовательские стили в качестве второго. Она возвращает функции для прослушивания кастомных событий:
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
Веб-компонент триггера содержит несколько полезных методов и свойств.
/**
* 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>;
}