UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

441 lines (373 loc) 13.5 kB
import { client } from '../plugins/Platform.js' import { createDirective } from '../utils/private/create.js' import { getModifierDirections, shouldStart } from '../utils/private/touch.js' import { addEvt, cleanEvt, position, leftClick, prevent, stop, stopAndPrevent, preventDraggable, noop } from '../utils/event.js' import { clearSelection } from '../utils/private/selection.js' import getSSRProps from '../utils/private/noop-ssr-directive-transform.js' function getChanges (evt, ctx, isFinal) { const pos = position(evt) let dir, distX = pos.left - ctx.event.x, distY = pos.top - ctx.event.y, absX = Math.abs(distX), absY = Math.abs(distY) const direction = ctx.direction if (direction.horizontal === true && direction.vertical !== true) { dir = distX < 0 ? 'left' : 'right' } else if (direction.horizontal !== true && direction.vertical === true) { dir = distY < 0 ? 'up' : 'down' } else if (direction.up === true && distY < 0) { dir = 'up' if (absX > absY) { if (direction.left === true && distX < 0) { dir = 'left' } else if (direction.right === true && distX > 0) { dir = 'right' } } } else if (direction.down === true && distY > 0) { dir = 'down' if (absX > absY) { if (direction.left === true && distX < 0) { dir = 'left' } else if (direction.right === true && distX > 0) { dir = 'right' } } } else if (direction.left === true && distX < 0) { dir = 'left' if (absX < absY) { if (direction.up === true && distY < 0) { dir = 'up' } else if (direction.down === true && distY > 0) { dir = 'down' } } } else if (direction.right === true && distX > 0) { dir = 'right' if (absX < absY) { if (direction.up === true && distY < 0) { dir = 'up' } else if (direction.down === true && distY > 0) { dir = 'down' } } } let synthetic = false if (dir === void 0 && isFinal === false) { if (ctx.event.isFirst === true || ctx.event.lastDir === void 0) { return {} } dir = ctx.event.lastDir synthetic = true if (dir === 'left' || dir === 'right') { pos.left -= distX absX = 0 distX = 0 } else { pos.top -= distY absY = 0 distY = 0 } } return { synthetic, payload: { evt, touch: ctx.event.mouse !== true, mouse: ctx.event.mouse === true, position: pos, direction: dir, isFirst: ctx.event.isFirst, isFinal: isFinal === true, duration: Date.now() - ctx.event.time, distance: { x: absX, y: absY }, offset: { x: distX, y: distY }, delta: { x: pos.left - ctx.event.lastX, y: pos.top - ctx.event.lastY } } } } let uid = 0 export default createDirective(__QUASAR_SSR_SERVER__ ? { name: 'touch-pan', getSSRProps } : { name: 'touch-pan', beforeMount (el, { value, modifiers }) { // early return, we don't need to do anything if (modifiers.mouse !== true && client.has.touch !== true) { return } function handleEvent (evt, mouseEvent) { if (modifiers.mouse === true && mouseEvent === true) { stopAndPrevent(evt) } else { modifiers.stop === true && stop(evt) modifiers.prevent === true && prevent(evt) } } const ctx = { uid: 'qvtp_' + (uid++), handler: value, modifiers, direction: getModifierDirections(modifiers), noop, mouseStart (evt) { if (shouldStart(evt, ctx) && leftClick(evt)) { addEvt(ctx, 'temp', [ [ document, 'mousemove', 'move', 'notPassiveCapture' ], [ document, 'mouseup', 'end', 'passiveCapture' ] ]) ctx.start(evt, true) } }, touchStart (evt) { if (shouldStart(evt, ctx)) { const target = evt.target addEvt(ctx, 'temp', [ [ target, 'touchmove', 'move', 'notPassiveCapture' ], [ target, 'touchcancel', 'end', 'passiveCapture' ], [ target, 'touchend', 'end', 'passiveCapture' ] ]) ctx.start(evt) } }, start (evt, mouseEvent) { client.is.firefox === true && preventDraggable(el, true) ctx.lastEvt = evt /* * Stop propagation so possible upper v-touch-pan don't catch this as well; * If we're not the target (based on modifiers), we'll re-emit the event later */ if (mouseEvent === true || modifiers.stop === true) { /* * are we directly switching to detected state? * clone event only otherwise */ if ( ctx.direction.all !== true // account for UMD too where modifiers will be lowercased to work && (mouseEvent !== true || (ctx.modifiers.mouseAllDir !== true && ctx.modifiers.mousealldir !== true)) ) { const clone = evt.type.indexOf('mouse') > -1 ? new MouseEvent(evt.type, evt) : new TouchEvent(evt.type, evt) evt.defaultPrevented === true && prevent(clone) evt.cancelBubble === true && stop(clone) Object.assign(clone, { qKeyEvent: evt.qKeyEvent, qClickOutside: evt.qClickOutside, qAnchorHandled: evt.qAnchorHandled, qClonedBy: evt.qClonedBy === void 0 ? [ ctx.uid ] : evt.qClonedBy.concat(ctx.uid) }) ctx.initialEvent = { target: evt.target, event: clone } } stop(evt) } const { left, top } = position(evt) ctx.event = { x: left, y: top, time: Date.now(), mouse: mouseEvent === true, detected: false, isFirst: true, isFinal: false, lastX: left, lastY: top } }, move (evt) { if (ctx.event === void 0) { return } const pos = position(evt), distX = pos.left - ctx.event.x, distY = pos.top - ctx.event.y // prevent buggy browser behavior (like Blink-based engine ones on Windows) // where the mousemove event occurs even if there's no movement after mousedown // https://bugs.chromium.org/p/chromium/issues/detail?id=161464 // https://bugs.chromium.org/p/chromium/issues/detail?id=721341 // https://github.com/quasarframework/quasar/issues/10721 if (distX === 0 && distY === 0) { return } ctx.lastEvt = evt const isMouseEvt = ctx.event.mouse === true const start = () => { handleEvent(evt, isMouseEvt) let cursor if (modifiers.preserveCursor !== true && modifiers.preservecursor !== true) { cursor = document.documentElement.style.cursor || '' document.documentElement.style.cursor = 'grabbing' } isMouseEvt === true && document.body.classList.add('no-pointer-events--children') document.body.classList.add('non-selectable') clearSelection() ctx.styleCleanup = withDelayedFn => { ctx.styleCleanup = void 0 if (cursor !== void 0) { document.documentElement.style.cursor = cursor } document.body.classList.remove('non-selectable') if (isMouseEvt === true) { const remove = () => { document.body.classList.remove('no-pointer-events--children') } if (withDelayedFn !== void 0) { setTimeout(() => { remove() withDelayedFn() }, 50) } else { remove() } } else if (withDelayedFn !== void 0) { withDelayedFn() } } } if (ctx.event.detected === true) { ctx.event.isFirst !== true && handleEvent(evt, ctx.event.mouse) const { payload, synthetic } = getChanges(evt, ctx, false) if (payload !== void 0) { if (ctx.handler(payload) === false) { ctx.end(evt) } else { if (ctx.styleCleanup === void 0 && ctx.event.isFirst === true) { start() } ctx.event.lastX = payload.position.left ctx.event.lastY = payload.position.top ctx.event.lastDir = synthetic === true ? void 0 : payload.direction ctx.event.isFirst = false } } return } if ( ctx.direction.all === true // account for UMD too where modifiers will be lowercased to work || (isMouseEvt === true && (ctx.modifiers.mouseAllDir === true || ctx.modifiers.mousealldir === true)) ) { start() ctx.event.detected = true ctx.move(evt) return } const absX = Math.abs(distX), absY = Math.abs(distY) if (absX !== absY) { if ( (ctx.direction.horizontal === true && absX > absY) || (ctx.direction.vertical === true && absX < absY) || (ctx.direction.up === true && absX < absY && distY < 0) || (ctx.direction.down === true && absX < absY && distY > 0) || (ctx.direction.left === true && absX > absY && distX < 0) || (ctx.direction.right === true && absX > absY && distX > 0) ) { ctx.event.detected = true ctx.move(evt) } else { ctx.end(evt, true) } } }, end (evt, abort) { if (ctx.event === void 0) { return } cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) if (abort === true) { ctx.styleCleanup !== void 0 && ctx.styleCleanup() if (ctx.event.detected !== true && ctx.initialEvent !== void 0) { ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event) } } else if (ctx.event.detected === true) { ctx.event.isFirst === true && ctx.handler(getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx).payload) const { payload } = getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx, true) const fn = () => { ctx.handler(payload) } if (ctx.styleCleanup !== void 0) { ctx.styleCleanup(fn) } else { fn() } } ctx.event = void 0 ctx.initialEvent = void 0 ctx.lastEvt = void 0 } } el.__qtouchpan = ctx if (modifiers.mouse === true) { // account for UMD too where modifiers will be lowercased to work const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : '' addEvt(ctx, 'main', [ [ el, 'mousedown', 'mouseStart', `passive${ capture }` ] ]) } client.has.touch === true && addEvt(ctx, 'main', [ [ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ], [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll) ]) }, updated (el, bindings) { const ctx = el.__qtouchpan if (ctx !== void 0) { if (bindings.oldValue !== bindings.value) { typeof value !== 'function' && ctx.end() ctx.handler = bindings.value } ctx.direction = getModifierDirections(bindings.modifiers) } }, beforeUnmount (el) { const ctx = el.__qtouchpan if (ctx !== void 0) { // emit the end event when the directive is destroyed while active // this is only needed in TouchPan because the rest of the touch directives do not emit an end event // the condition is also checked in the start of function but we avoid the call ctx.event !== void 0 && ctx.end() cleanEvt(ctx, 'main') cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) ctx.styleCleanup !== void 0 && ctx.styleCleanup() delete el.__qtouchpan } } } )