UNPKG

@shopify/draggable

Version:

The JavaScript Drag & Drop library your grandparents warned you about.

249 lines (231 loc) 7.07 kB
import Draggable from '../Draggable/Draggable.mjs'; import '../Draggable/DragEvent/DragEvent.mjs'; import '../Draggable/DraggableEvent/DraggableEvent.mjs'; import '../Draggable/Plugins/Announcement/Announcement.mjs'; import '../Draggable/Plugins/Mirror/MirrorEvent/MirrorEvent.mjs'; import '../Draggable/Sensors/SensorEvent/SensorEvent.mjs'; import '../Draggable/Sensors/TouchSensor/TouchSensor.mjs'; import { SortableStartEvent, SortableSortEvent, SortableSortedEvent, SortableStopEvent } from './SortableEvent/SortableEvent.mjs'; const onDragStart = Symbol('onDragStart'); const onDragOverContainer = Symbol('onDragOverContainer'); const onDragOver = Symbol('onDragOver'); const onDragStop = Symbol('onDragStop'); function onSortableSortedDefaultAnnouncement({ dragEvent }) { const sourceText = dragEvent.source.textContent.trim() || dragEvent.source.id || 'sortable element'; if (dragEvent.over) { const overText = dragEvent.over.textContent.trim() || dragEvent.over.id || 'sortable element'; const isFollowing = dragEvent.source.compareDocumentPosition(dragEvent.over) & Node.DOCUMENT_POSITION_FOLLOWING; if (isFollowing) { return `Placed ${sourceText} after ${overText}`; } else { return `Placed ${sourceText} before ${overText}`; } } else { return `Placed ${sourceText} into a different container`; } } const defaultAnnouncements = { 'sortable:sorted': onSortableSortedDefaultAnnouncement }; class Sortable extends Draggable { constructor(containers = [], options = {}) { super(containers, { ...options, announcements: { ...defaultAnnouncements, ...(options.announcements || {}) } }); this.startIndex = null; this.startContainer = null; this[onDragStart] = this[onDragStart].bind(this); this[onDragOverContainer] = this[onDragOverContainer].bind(this); this[onDragOver] = this[onDragOver].bind(this); this[onDragStop] = this[onDragStop].bind(this); this.on('drag:start', this[onDragStart]).on('drag:over:container', this[onDragOverContainer]).on('drag:over', this[onDragOver]).on('drag:stop', this[onDragStop]); } destroy() { super.destroy(); this.off('drag:start', this[onDragStart]).off('drag:over:container', this[onDragOverContainer]).off('drag:over', this[onDragOver]).off('drag:stop', this[onDragStop]); } index(element) { return this.getSortableElementsForContainer(element.parentNode).indexOf(element); } getSortableElementsForContainer(container) { const allSortableElements = container.querySelectorAll(this.options.draggable); return [...allSortableElements].filter(childElement => { return childElement !== this.originalSource && childElement !== this.mirror && childElement.parentNode === container; }); } [onDragStart](event) { this.startContainer = event.source.parentNode; this.startIndex = this.index(event.source); const sortableStartEvent = new SortableStartEvent({ dragEvent: event, startIndex: this.startIndex, startContainer: this.startContainer }); this.trigger(sortableStartEvent); if (sortableStartEvent.canceled()) { event.cancel(); } } [onDragOverContainer](event) { if (event.canceled()) { return; } const { source, over, overContainer } = event; const oldIndex = this.index(source); const sortableSortEvent = new SortableSortEvent({ dragEvent: event, currentIndex: oldIndex, source, over }); this.trigger(sortableSortEvent); if (sortableSortEvent.canceled()) { return; } const children = this.getSortableElementsForContainer(overContainer); const moves = move({ source, over, overContainer, children }); if (!moves) { return; } const { oldContainer, newContainer } = moves; const newIndex = this.index(event.source); const sortableSortedEvent = new SortableSortedEvent({ dragEvent: event, oldIndex, newIndex, oldContainer, newContainer }); this.trigger(sortableSortedEvent); } [onDragOver](event) { if (event.over === event.originalSource || event.over === event.source) { return; } const { source, over, overContainer } = event; const oldIndex = this.index(source); const sortableSortEvent = new SortableSortEvent({ dragEvent: event, currentIndex: oldIndex, source, over }); this.trigger(sortableSortEvent); if (sortableSortEvent.canceled()) { return; } const children = this.getDraggableElementsForContainer(overContainer); const moves = move({ source, over, overContainer, children }); if (!moves) { return; } const { oldContainer, newContainer } = moves; const newIndex = this.index(source); const sortableSortedEvent = new SortableSortedEvent({ dragEvent: event, oldIndex, newIndex, oldContainer, newContainer }); this.trigger(sortableSortedEvent); } [onDragStop](event) { const sortableStopEvent = new SortableStopEvent({ dragEvent: event, oldIndex: this.startIndex, newIndex: this.index(event.source), oldContainer: this.startContainer, newContainer: event.source.parentNode }); this.trigger(sortableStopEvent); this.startIndex = null; this.startContainer = null; } } function index(element) { return Array.prototype.indexOf.call(element.parentNode.children, element); } function move({ source, over, overContainer, children }) { const emptyOverContainer = !children.length; const differentContainer = source.parentNode !== overContainer; const sameContainer = over && source.parentNode === over.parentNode; if (emptyOverContainer) { return moveInsideEmptyContainer(source, overContainer); } else if (sameContainer) { return moveWithinContainer(source, over); } else if (differentContainer) { return moveOutsideContainer(source, over, overContainer); } else { return null; } } function moveInsideEmptyContainer(source, overContainer) { const oldContainer = source.parentNode; overContainer.appendChild(source); return { oldContainer, newContainer: overContainer }; } function moveWithinContainer(source, over) { const oldIndex = index(source); const newIndex = index(over); if (oldIndex < newIndex) { source.parentNode.insertBefore(source, over.nextElementSibling); } else { source.parentNode.insertBefore(source, over); } return { oldContainer: source.parentNode, newContainer: source.parentNode }; } function moveOutsideContainer(source, over, overContainer) { const oldContainer = source.parentNode; if (over) { over.parentNode.insertBefore(source, over); } else { overContainer.appendChild(source); } return { oldContainer, newContainer: source.parentNode }; } export { Sortable as default };