UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

264 lines (263 loc) • 8.6 kB
/** * DevExtreme (esm/__internal/events/gesture/m_emitter.gesture.scroll.js) * Version: 24.2.7 * Build date: Mon Apr 28 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import { cancelAnimationFrame, requestAnimationFrame } from "../../../common/core/animation/frame"; import registerEmitter from "../../../common/core/events/core/emitter_registrator"; import eventsEngine from "../../../common/core/events/core/events_engine"; import GestureEmitter from "../../../common/core/events/gesture/emitter.gesture"; import { addNamespace, eventData, eventDelta, isDxMouseWheelEvent, isMouseEvent } from "../../../common/core/events/utils/index"; import Class from "../../../core/class"; import devices from "../../core/m_devices"; const { abstract: abstract } = Class; const realDevice = devices.real(); const SCROLL_EVENT = "scroll"; const SCROLL_INIT_EVENT = "dxscrollinit"; const SCROLL_START_EVENT = "dxscrollstart"; const SCROLL_MOVE_EVENT = "dxscroll"; const SCROLL_END_EVENT = "dxscrollend"; const SCROLL_STOP_EVENT = "dxscrollstop"; const SCROLL_CANCEL_EVENT = "dxscrollcancel"; const Locker = Class.inherit(function() { const NAMESPACED_SCROLL_EVENT = addNamespace("scroll", "dxScrollEmitter"); return { ctor(element) { this._element = element; this._locked = false; this._proxiedScroll = e => { if (!this._disposed) { this._scroll(e) } }; eventsEngine.on(this._element, NAMESPACED_SCROLL_EVENT, this._proxiedScroll) }, _scroll: abstract, check(e, callback) { if (this._locked) { callback() } }, dispose() { this._disposed = true; eventsEngine.off(this._element, NAMESPACED_SCROLL_EVENT, this._proxiedScroll) } } }()); const TimeoutLocker = Locker.inherit({ ctor(element, timeout) { this.callBase(element); this._timeout = timeout }, _scroll() { this._prepare(); this._forget() }, _prepare() { if (this._timer) { this._clearTimer() } this._locked = true }, _clearTimer() { clearTimeout(this._timer); this._locked = false; this._timer = null }, _forget() { const that = this; this._timer = setTimeout((() => { that._clearTimer() }), this._timeout) }, dispose() { this.callBase(); this._clearTimer() } }); const WheelLocker = TimeoutLocker.inherit({ ctor(element) { this.callBase(element, 400); this._lastWheelDirection = null }, check(e, callback) { this._checkDirectionChanged(e); this.callBase(e, callback) }, _checkDirectionChanged(e) { if (!isDxMouseWheelEvent(e)) { this._lastWheelDirection = null; return } const direction = e.shiftKey || false; const directionChange = null !== this._lastWheelDirection && direction !== this._lastWheelDirection; this._lastWheelDirection = direction; this._locked = this._locked && !directionChange } }); let PointerLocker = TimeoutLocker.inherit({ ctor(element) { this.callBase(element, 400) } }); ! function() { const { ios: isIos, android: isAndroid } = realDevice; if (!(isIos || isAndroid)) { return } PointerLocker = Locker.inherit({ _scroll() { this._locked = true; const that = this; cancelAnimationFrame(this._scrollFrame); this._scrollFrame = requestAnimationFrame((() => { that._locked = false })) }, check(e, callback) { cancelAnimationFrame(this._scrollFrame); cancelAnimationFrame(this._checkFrame); const that = this; const { callBase: callBase } = this; this._checkFrame = requestAnimationFrame((() => { callBase.call(that, e, callback); that._locked = false })) }, dispose() { this.callBase(); cancelAnimationFrame(this._scrollFrame); cancelAnimationFrame(this._checkFrame) } }) }(); const ScrollEmitter = GestureEmitter.inherit(function() { const FRAME_DURATION = Math.round(1e3 / 60); return { ctor(element) { this.callBase.apply(this, arguments); this.direction = "both"; this._pointerLocker = new PointerLocker(element); this._wheelLocker = new WheelLocker(element) }, validate: () => true, configure(data) { if (data.scrollTarget) { this._pointerLocker.dispose(); this._wheelLocker.dispose(); this._pointerLocker = new PointerLocker(data.scrollTarget); this._wheelLocker = new WheelLocker(data.scrollTarget) } this.callBase(data) }, _init(e) { this._wheelLocker.check(e, (() => { if (isDxMouseWheelEvent(e)) { this._accept(e) } })); this._pointerLocker.check(e, (() => { const skipCheck = this.isNative && isMouseEvent(e); if (!isDxMouseWheelEvent(e) && !skipCheck) { this._accept(e) } })); this._fireEvent("dxscrollinit", e); this._prevEventData = eventData(e) }, move(e) { this.callBase.apply(this, arguments); e.isScrollingEvent = this.isNative || e.isScrollingEvent }, _start(e) { this._savedEventData = eventData(e); this._fireEvent("dxscrollstart", e); this._prevEventData = eventData(e) }, _move(e) { const currentEventData = eventData(e); this._fireEvent("dxscroll", e, { delta: eventDelta(this._prevEventData, currentEventData) }); const delta = eventDelta(this._savedEventData, currentEventData); if (delta.time > 200) { this._savedEventData = this._prevEventData } this._prevEventData = eventData(e) }, _end(e) { const endEventDelta = eventDelta(this._prevEventData, eventData(e)); let velocity = { x: 0, y: 0 }; if (!isDxMouseWheelEvent(e) && endEventDelta.time < 100) { const delta = eventDelta(this._savedEventData, this._prevEventData); const velocityMultiplier = FRAME_DURATION / delta.time; velocity = { x: delta.x * velocityMultiplier, y: delta.y * velocityMultiplier } } this._fireEvent("dxscrollend", e, { velocity: velocity }) }, _stop(e) { this._fireEvent("dxscrollstop", e) }, cancel(e) { this.callBase.apply(this, arguments); this._fireEvent("dxscrollcancel", e) }, dispose() { this.callBase.apply(this, arguments); this._pointerLocker.dispose(); this._wheelLocker.dispose() }, _clearSelection() { if (this.isNative) { return } return this.callBase.apply(this, arguments) }, _toggleGestureCover() { if (this.isNative) { return } return this.callBase.apply(this, arguments) } } }()); registerEmitter({ emitter: ScrollEmitter, events: ["dxscrollinit", "dxscrollstart", "dxscroll", "dxscrollend", "dxscrollstop", "dxscrollcancel"] }); export default { init: "dxscrollinit", start: "dxscrollstart", move: "dxscroll", end: "dxscrollend", stop: "dxscrollstop", cancel: "dxscrollcancel", scroll: "scroll" };