Handle draggable lines on touch screens

This commit is contained in:
axolotle 2025-05-13 12:26:21 +02:00 committed by John Livingston
parent 048c003ff0
commit d79deef5c0
2 changed files with 73 additions and 20 deletions

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
//
// SPDX-License-Identifier: AGPL-3.0-only
@ -11,6 +12,7 @@ import './styles/draggables.scss'
*/
export class DraggablesCustomElement extends CustomElement {
currentDragged = null
droppableEl = null
/**
* The tag name for draggable elements.
@ -37,6 +39,9 @@ export class DraggablesCustomElement extends CustomElement {
this._handleDragLeaveBinded = this._handleDragLeave.bind(this)
this._handleDragEndBinded = this._handleDragEnd.bind(this)
this._handleDropBinded = this._handleDrop.bind(this)
this._handleTouchStartBinded = this._handleTouchStart.bind(this)
this._handleTouchMoveBinded = this._handleTouchMove.bind(this)
this._handleTouchEndBinded = this._handleTouchEnd.bind(this)
return super.initialize()
}
@ -44,21 +49,29 @@ export class DraggablesCustomElement extends CustomElement {
connectedCallback () {
super.connectedCallback()
this.currentDragged = null
this.droppableEl = null
this.addEventListener('dragstart', this._handleDragStartBinded)
this.addEventListener('dragover', this._handleDragOverBinded)
this.addEventListener('dragleave', this._handleDragLeaveBinded)
this.addEventListener('dragend', this._handleDragEndBinded)
this.addEventListener('drop', this._handleDropBinded)
this.addEventListener('touchstart', this._handleTouchStartBinded)
this.addEventListener('touchmove', this._handleTouchMoveBinded)
this.addEventListener('touchend', this._handleTouchEndBinded)
}
disconnectedCallback () {
super.disconnectedCallback()
this.currentDragged = null
this.droppableEl = null
this.removeEventListener('dragstart', this._handleDragStartBinded)
this.removeEventListener('dragover', this._handleDragOverBinded)
this.removeEventListener('dragleave', this._handleDragLeaveBinded)
this.removeEventListener('dragend', this._handleDragEndBinded)
this.removeEventListener('drop', this._handleDropBinded)
this.removeEventListener('touchstart', this._handleTouchStartBinded)
this.removeEventListener('touchmove', this._handleTouchMoveBinded)
this.removeEventListener('touchend', this._handleTouchEndBinded)
}
_isADraggableEl (target) {
@ -69,8 +82,7 @@ export class DraggablesCustomElement extends CustomElement {
return target.closest?.(this.droppableTagNames.join(','))
}
_isOnTopHalf (ev, el) {
const y = ev.clientY
_isOnTopHalf (y, el) {
const bounding = el.getBoundingClientRect()
return (y <= bounding.y + (bounding.height / 2))
}
@ -81,13 +93,39 @@ export class DraggablesCustomElement extends CustomElement {
)
}
_setCurrentDragged (draggedEl) {
console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...')
this.currentDragged = draggedEl
this._resetDropOver()
}
_setDroppableClasses (droppableEl, y) {
// Are we on the top or bottom part of the droppableEl?
let topHalf = false
if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) {
topHalf = this._isOnTopHalf(y, droppableEl)
}
droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half')
droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half')
}
_tryToDrop (droppedOnEl) {
if (!droppedOnEl) return
console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...')
try {
this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half'))
} catch (err) {
console.error(err)
}
this._resetDropOver()
}
_handleDragStart (ev) {
// The draggable=true is on a child bode
const possibleEl = ev.target.parentElement
if (!this._isADraggableEl(possibleEl)) { return }
console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...')
this.currentDragged = possibleEl
this._resetDropOver()
this._setCurrentDragged(possibleEl)
}
_handleDragOver (ev) {
@ -97,14 +135,7 @@ export class DraggablesCustomElement extends CustomElement {
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault
ev.preventDefault()
// Are we on the top or bottom part of the droppableEl?
let topHalf = false
if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) {
topHalf = this._isOnTopHalf(ev, droppableEl)
}
droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half')
droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half')
this._setDroppableClasses(droppableEl, ev.clientY)
}
_handleDragLeave (ev) {
@ -124,16 +155,33 @@ export class DraggablesCustomElement extends CustomElement {
let droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half')
droppedOnEl = this._getParentDroppableEl(droppedOnEl)
if (!droppedOnEl) { return }
console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...')
try {
this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half'))
} catch (err) {
console.error(err)
this._tryToDrop(droppedOnEl)
}
_handleTouchStart (ev) {
const possibleEl = this._getParentDroppableEl(ev.target)
if (!possibleEl) return
this._setCurrentDragged(possibleEl)
}
_handleTouchMove (ev) {
if (!this.currentDragged) return
const { clientX, clientY } = ev.touches[0]
const droppableEl = this._getParentDroppableEl(document.elementFromPoint(clientX, clientY))
if (!droppableEl || droppableEl === this.droppableEl) return
this.droppableEl = droppableEl
this._resetDropOver()
this._setDroppableClasses(droppableEl, clientY)
}
_handleTouchEnd (_ev) {
if (!this.currentDragged || !this.droppableEl) return
this._tryToDrop(this.droppableEl)
this.currentDragged = null
this.droppableEl = null
}
/**

View File

@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
* SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -16,4 +17,8 @@
.livechat-drag-top-half > .draggables-line {
border-top: 4px solid blue;
}
[draggable="true"] {
touch-action: none;
}
}