84 lines
2.3 KiB
JavaScript
84 lines
2.3 KiB
JavaScript
import { Controller } from "@hotwired/stimulus"
|
|
|
|
const MOVE_ITEM_DATA_TYPE = "x-fizzy/move"
|
|
const DIVIDER_ITEM_NODE_NAME = "LI"
|
|
const OVERLAP_THRESHOLD = 0.25
|
|
|
|
export default class extends Controller {
|
|
static targets = [ "divider", "dragImage", "count" ]
|
|
static classes = [ "installed", "dragging" ]
|
|
static values = { startCount: Number, maxCount: Number }
|
|
|
|
connect() {
|
|
this.install()
|
|
}
|
|
|
|
install() {
|
|
this.#moveDividerTo(this.startCountValue)
|
|
this.dividerTarget.classList.add(this.installedClass)
|
|
}
|
|
|
|
configureDrag(event) {
|
|
if (event.target == this.dividerTarget) {
|
|
event.dataTransfer.dropEffect = "move"
|
|
event.dataTransfer.setData(MOVE_ITEM_DATA_TYPE, event.target)
|
|
event.dataTransfer.setDragImage(this.dragImageTarget, 0, 0)
|
|
this.element.classList.add(this.draggingClass)
|
|
}
|
|
}
|
|
|
|
acceptDrop(event) {
|
|
if (event.dataTransfer.types.includes(MOVE_ITEM_DATA_TYPE)) {
|
|
event.preventDefault()
|
|
}
|
|
}
|
|
|
|
moveDivider(event) {
|
|
if (event.target.nodeName == DIVIDER_ITEM_NODE_NAME) {
|
|
const rect = this.dividerTarget.getBoundingClientRect()
|
|
const distanceToTop = Math.abs(event.clientY - rect.top)
|
|
const distanceToBottom = Math.abs(event.clientY - (rect.top + rect.height))
|
|
const distanceToNearestEdge = Math.min(distanceToTop, distanceToBottom)
|
|
const overlap = distanceToNearestEdge / rect.height
|
|
|
|
if (overlap > OVERLAP_THRESHOLD) {
|
|
this.#moveDividerTo(this.#items.indexOf(event.target))
|
|
}
|
|
}
|
|
}
|
|
|
|
drop() {
|
|
this.element.classList.remove(this.draggingClass)
|
|
}
|
|
|
|
#moveDividerTo(index) {
|
|
if (index <= this.maxCountValue) {
|
|
if (this.#dividerIndex < index) {
|
|
this.#positionDividerAfter(index)
|
|
} else if (this.#dividerIndex > index) {
|
|
this.#positionDividerBefore(index)
|
|
}
|
|
}
|
|
}
|
|
|
|
#positionDividerBefore(index) {
|
|
const position = Math.max(index, 1)
|
|
this.#items[position].before(this.dividerTarget)
|
|
this.countTarget.textContent = position
|
|
}
|
|
|
|
#positionDividerAfter(index) {
|
|
const position = Math.min(index, this.#items.length - 1, this.maxCountValue)
|
|
this.#items[position].after(this.dividerTarget)
|
|
this.countTarget.textContent = position
|
|
}
|
|
|
|
get #items() {
|
|
return Array.from(this.element.children)
|
|
}
|
|
|
|
get #dividerIndex() {
|
|
return this.#items.indexOf(this.dividerTarget)
|
|
}
|
|
}
|