import get from 'lodash-es/get.js'
import isBoolean from 'lodash-es/isBoolean.js'
import isFunction from 'lodash-es/isFunction.js'
import evem from './evem.mjs'
import domCancelEvent from './domCancelEvent.mjs'
/**
* 前端監聽指定panel與bar的DOM元素,可偵測滾輪與拖曳bar事件
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/domDragBarAndScroll.test.mjs Github}
* @memberOf wsemi
* @param {HTMLElement} panel 輸入panel元素
* @param {HTMLElement} bar 輸入bar元素
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Function} [getHeighRatio=()=>1] 輸入取得高度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前heighRatio,預設()=>1
* @param {Function} [getWidthRatio=()=>1] 輸入取得寬度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前widthRatio,預設()=>1
* @param {Boolean} [stopScrollPropagationForPanel=false] 輸入是否停用滑鼠捲動事件向上傳遞布林值,預設false
* @param {Boolean} [stopTouchDragPropagationForPanel=false] 輸入是否停用手機拖曳事件向上傳遞布林值,預設false
* @param {Boolean} [useTouchDragForPanel=true] 輸入是否使用手機拖曳事件布林值,預設true
* @returns {Object} 回傳物件,可使用on與clear函數,on可監聽pressBar、dragBar、freeBar事件,clear為釋放監聽
* @example
* need test in browser
*
* //監聽dom
* let divPanel = document.querySelector('#id_panel')
* let divBar = document.querySelector('#id_bar')
* let getHeighRatio = () => 0.9
* let das = domDragBarAndScroll(divPanel, divBar, { getHeighRatio, stopScrollPropagationForPanel: true, stopTouchDragPropagationForPanel: true })
* das.on('scrollPanel', () => {})
* das.on('pressBar', () => {})
* das.on('dragBar', () => {})
* das.on('freeBar', () => {})
*
* //釋放監聽
* das.clear()
*
*/
function domDragBarAndScroll(panel, bar, opt = {}) {
let fPanelWheel = null
let fPanelTouchstart = null
let fPanelTouchmove = null
let fPanelTouchend = null
let fBarMousedown = null
let fBarMouseup = null
let fBarTouchstart = null
let fBarTouchmove = null
let fBarTouchend = null
let fWindowMousemove = null
let fWindowMouseup = null
let fWindowScroll = null
let fWindowWheel = null
let fDocumentScroll = null
let fDocumentWheel = null
let barPressing = false
//getHeighRatio, 因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前heighRatio
let getHeighRatio = get(opt, 'getHeighRatio', null)
if (!isFunction(getHeighRatio)) {
getHeighRatio = () => 1
}
//getWidthRatio, 因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前widthRatio
let getWidthRatio = get(opt, 'getWidthRatio', null)
if (!isFunction(getWidthRatio)) {
getWidthRatio = () => 1
}
//stopScrollPropagationForPanel
let stopScrollPropagationForPanel = get(opt, 'stopScrollPropagationForPanel', null)
if (!isBoolean(stopScrollPropagationForPanel)) {
stopScrollPropagationForPanel = false
}
//stopTouchDragPropagationForPanel
let stopTouchDragPropagationForPanel = get(opt, 'stopTouchDragPropagationForPanel', null)
if (!isBoolean(stopTouchDragPropagationForPanel)) {
stopTouchDragPropagationForPanel = false
}
//useTouchDragForPanel
let useTouchDragForPanel = get(opt, 'useTouchDragForPanel', null)
if (!isBoolean(useTouchDragForPanel)) {
useTouchDragForPanel = true
}
//ele
let elePanel = panel
let eleBar = bar
//ev
let ev = evem()
function add() {
//fPanelWheel
fPanelWheel = (e) => {
let ry = e.deltaY / Math.abs(e.deltaY)
let rx = e.deltaX / Math.abs(e.deltaX)
ev.emit('scrollPanel', { ratioY: ry, ratioX: rx }) //寬版頁面, 用滾輪上下捲動, 實際是傳移動距離給bar
if (stopScrollPropagationForPanel) {
domCancelEvent(e) //要禁止外部元素如body被滑鼠捲動
}
}
elePanel.addEventListener('wheel', fPanelWheel)
//fPanelTouchstart
fPanelTouchstart = (e) => {
if (useTouchDragForPanel) {
//acy, acx
let acy = get(e, `touches.0.clientY`, 0)
let acx = get(e, `touches.0.clientX`, 0)
//cy, cx
let cy = -acy * getHeighRatio()
let cx = -acx * getWidthRatio()
//barPressing
barPressing = true
//emit
ev.emit('pressBar', { clientY: cy, clientX: cx }) //窄版頁面, 上鎖與紀錄頁面點擊y座標, 僅紀錄第一觸擊點座標, 另需被heighRatio修正比例
//cancel
if (stopTouchDragPropagationForPanel) {
domCancelEvent(e)
}
}
}
elePanel.addEventListener('touchstart', fPanelTouchstart)
//fPanelTouchmove
fPanelTouchmove = (e) => {
if (useTouchDragForPanel && barPressing) {
//acy, acx
let acy = get(e, `touches.0.clientY`, 0)
let acx = get(e, `touches.0.clientX`, 0)
// //check, 若水平向分量滑動較多, 則清空垂直向移動並結束
// if (acx > 0 && acy > 0 && Math.abs(acx) >= Math.abs(acy)) {
// e.touches[0].clientY = 0 //Cannot set property clientY of #<Touch> which has only a getter
// return
// }
//cy, cx
let cy = -acy * getHeighRatio()
let cx = -acx * getWidthRatio()
//emit
ev.emit('dragBar', { clientY: cy, clientX: cx }) //窄版頁面, 用滑動距離拖曳頁面, 實際是傳移動距離給bar, 僅紀錄第一觸擊點座標, 另需被heighRatio修正比例
//cancel
if (stopTouchDragPropagationForPanel) {
domCancelEvent(e) //要禁止外部元素如body被拖曳移動畫面, 此會導致無法左右拖曳元素(導致開啟overflow-x:auto無法使用)
}
}
}
elePanel.addEventListener('touchmove', fPanelTouchmove, { passive: false }) //必須使用passive=false否則無法cancel
//fPanelTouchend
fPanelTouchend = (e) => {
if (useTouchDragForPanel && barPressing) {
//barPressing
barPressing = false
//emit
ev.emit('freeBar') //窄版頁面, 解鎖
//cancel
if (stopTouchDragPropagationForPanel) {
domCancelEvent(e)
}
}
}
elePanel.addEventListener('touchend', fPanelTouchend)
//fBarMousedown
fBarMousedown = (e) => {
barPressing = true
ev.emit('pressBar', { clientY: e.clientY, clientX: e.clientX }) //寬版bar, 上鎖與紀錄點擊y座標
//domCancelEvent(e)
}
eleBar.addEventListener('mousedown', fBarMousedown)
//fBarMouseup, 可由fWindowMouseup自動解鎖, 不過若嵌入panel有攔截mouseup事件(例如popup)會導致外面window收不到mouseup事件, 故bar的mouseup事件仍需要監聽處理解鎖行為
fBarMouseup = (e) => {
if (barPressing) {
barPressing = false
ev.emit('freeBar') //窄版bar, 解鎖
//domCancelEvent(e)
}
}
eleBar.addEventListener('mouseup', fBarMouseup)
//fBarTouchstart
fBarTouchstart = (e) => {
barPressing = true
ev.emit('pressBar', { clientY: e.touches[0].clientY, clientX: e.touches[0].clientX }) //窄版bar, 上鎖與紀錄bar點擊y座標
//domCancelEvent(e)
}
eleBar.addEventListener('touchstart', fBarTouchstart)
//fBarTouchmove
fBarTouchmove = (e) => {
if (barPressing) {
ev.emit('dragBar', { clientY: e.touches[0].clientY, clientX: e.touches[0].clientX }) //窄版bar, 用滑動距離拖曳bar, 實際是傳移動距離給bar
domCancelEvent(e) //要禁止外部元素如body被拖曳移動畫面
}
}
eleBar.addEventListener('touchmove', fBarTouchmove, { passive: false }) //必須使用passive=false否則無法cancel
//fBarTouchend
fBarTouchend = (e) => {
if (barPressing) {
barPressing = false
ev.emit('freeBar') //窄版bar, 解鎖
//domCancelEvent(e)
}
}
eleBar.addEventListener('touchend', fBarTouchend)
//fWindowMousemove
fWindowMousemove = (e) => {
if (barPressing) {
ev.emit('dragBar', { clientY: e.clientY, clientX: e.clientX }) //寬版bar, 用鎖與滑動距離拖曳bar
//domCancelEvent(e)
}
}
window.addEventListener('mousemove', fWindowMousemove)
//fWindowMouseup
fWindowMouseup = (e) => {
if (barPressing) {
barPressing = false
ev.emit('freeBar') //寬版bar, 解鎖
if (stopScrollPropagationForPanel) {
domCancelEvent(e) //要禁止回傳否則會連外部body捲軸一起移動畫面
}
}
}
window.addEventListener('mouseup', fWindowMouseup)
//fWindowScroll
fWindowScroll = (e) => {
// if (barPressing) {
// console.log('cancel window scroll')
// //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件
// //此事件無法取消(cancelable=false), 即便使用passive=false
// domCancelEvent(e)
// }
}
window.addEventListener('scroll', fWindowScroll, { passive: false })
//fWindowWheel
fWindowWheel = (e) => {
// if (barPressing) {
// console.log('cancel window wheel')
// //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件
// //此事件無法取消(cancelable=false), 即便使用passive=false
// domCancelEvent(e)
// }
}
window.addEventListener('wheel', fWindowWheel, { passive: false })
//fDocumentScroll
fDocumentScroll = (e) => {
// if (barPressing) {
// console.log('cancel document scroll')
// //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件
// //此事件無法取消(cancelable=false), 即便使用passive=false
// domCancelEvent(e)
// }
}
document.addEventListener('scroll', fDocumentScroll, { passive: false })
//fDocumentWheel
fDocumentWheel = (e) => {
// if (barPressing) {
// console.log('cancel document wheel')
// //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件
// //此事件無法取消(cancelable=false), 即便使用passive=false
// domCancelEvent(e)
// }
}
document.addEventListener('wheel', fDocumentWheel, { passive: false })
}
function clear() {
//elePanel remove wheel, touchstart, touchmove, touchend
elePanel.removeEventListener('wheel', fPanelWheel)
elePanel.removeEventListener('touchstart', fPanelTouchstart)
elePanel.removeEventListener('touchmove', fPanelTouchmove)
elePanel.removeEventListener('touchend', fPanelTouchend)
//eleBar remove mousedown, mouseup, touchstart, touchmove, touchend
eleBar.removeEventListener('mousedown', fBarMousedown)
eleBar.removeEventListener('mouseup', fBarMouseup)
eleBar.removeEventListener('touchstart', fBarTouchstart)
eleBar.removeEventListener('touchmove', fBarTouchmove)
eleBar.removeEventListener('touchend', fBarTouchend)
//window remove mousemove, mouseup, wheel
window.removeEventListener('mousemove', fWindowMousemove)
window.removeEventListener('mouseup', fWindowMouseup)
window.removeEventListener('scroll', fWindowScroll)
window.removeEventListener('wheel', fWindowWheel)
//document remove scroll, wheel
document.removeEventListener('scroll', fDocumentScroll)
document.removeEventListener('wheel', fDocumentWheel)
}
//add
add()
//save
ev.clear = clear
return ev
}
export default domDragBarAndScroll