UNPKG

@alegendstale/holly-components

Version:

Reusable UI components created using lit

122 lines (121 loc) 5.1 kB
export class DragDrop { constructor(dropzones, draggables, onDrop) { this.dropzones = dropzones; this.draggables = draggables; this.result = { beforeElement: null, insertedElement: null, afterElement: null, order: this.draggables }; this.onDrop = onDrop; this.load(); } dragStart(draggable, e) { draggable.classList.toggle('is-dragging', true); } dragOver(dropzone, e) { if (e.dataTransfer) { const hasElement = Array.from(e.dataTransfer.items).some((item) => { return item.kind !== 'element'; }); // Return early if element being dragged is not an element if (hasElement) return; } e.preventDefault(); this.result.beforeElement = this.getDragBeforeElement(e.clientX); this.result.insertedElement = this.getDraggingElement(); this.result.afterElement = this.getDragAfterElement(e.clientX); const insertedIndex = this.draggables.indexOf(this.result.insertedElement); // If at the end of draggables if (this.result.afterElement === null) { // Append element dropzone.appendChild(this.result.insertedElement); // Remove element from order this.result.order.splice(insertedIndex, 1); // Add to end of order this.result.order.push(this.result.insertedElement); } else { // Place element between dropzone.insertBefore(this.result.insertedElement, this.result.afterElement); // Remove element from order this.result.order.splice(insertedIndex, 1); // Get the after index, post element removal const afterIndex = this.draggables.indexOf(this.result.afterElement); // Insert the element before the after index this.result.order.splice(afterIndex, 0, this.result.insertedElement); } } dragEnd(draggable, e) { draggable.classList.toggle('is-dragging', false); this.onDrop(e, this.result); this.result = { beforeElement: null, insertedElement: null, afterElement: null, order: this.draggables }; } load() { for (let draggable of this.draggables) { // Important, so that all types of elements can be dragged, including divs & spans draggable.setAttribute('draggable', "true"); draggable.addEventListener("dragstart", (e) => this.dragStart(draggable, e)); draggable.addEventListener("dragend", (e) => this.dragEnd(draggable, e)); } for (let dropzone of this.dropzones) { dropzone.addEventListener("dragover", (e) => this.dragOver(dropzone, e)); } } unload() { for (let draggable of this.draggables) { draggable.removeEventListener('dragstart', (e) => this.dragStart(draggable, e)); draggable.removeEventListener('dragend', (e) => this.dragEnd(draggable, e)); } for (let dropzone of this.dropzones) { dropzone.removeEventListener('dragover', (e) => this.dragOver(dropzone, e)); } } /** * @returns The element which is being dragged */ getDraggingElement() { return this.draggables.filter((draggable) => draggable.classList.contains('is-dragging'))[0]; } /** * @returns All draggable elements not being dragged */ getDraggableElements() { return this.draggables.filter((draggable) => !draggable.classList.contains('is-dragging')); } /** * Gets the closest element before the dragged element */ getDragBeforeElement(x) { return this.getDraggableElements().reduce((closest, child) => { const rect = child.getBoundingClientRect(); const leftPosition = x - rect.left; const halfChildWidth = rect.width / 2; const offset = leftPosition - halfChildWidth; // Return the closest element to the right of the cursor if (offset > 0 && offset < closest.offset) return { offset, element: child }; else return closest; }, // Initial closest value { offset: Number.POSITIVE_INFINITY, element: null }) .element; } /** * Gets the closest element after the dragged element */ getDragAfterElement(x) { return this.getDraggableElements().reduce((closest, child) => { const rect = child.getBoundingClientRect(); const leftPosition = x - rect.left; const halfChildWidth = rect.width / 2; const offset = leftPosition - halfChildWidth; // Return the closest element to the left of the cursor if (offset < 0 && offset > closest.offset) return { offset, element: child }; else return closest; }, // Initial closest value { offset: Number.NEGATIVE_INFINITY, element: null }) .element; } }