devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
956 lines (954 loc) • 37.8 kB
JavaScript
/**
* DevExtreme (cjs/ui/scroll_view/ui.scrollable.simulated.js)
* Version: 23.2.6
* Build date: Wed May 01 2024
*
* Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
exports.SimulatedStrategy = exports.Scroller = void 0;
var _size = require("../../core/utils/size");
var _renderer = _interopRequireDefault(require("../../core/renderer"));
var _dom_adapter = _interopRequireDefault(require("../../core/dom_adapter"));
var _events_engine = _interopRequireDefault(require("../../events/core/events_engine"));
var _inflector = require("../../core/utils/inflector");
var _extend = require("../../core/utils/extend");
var _window = require("../../core/utils/window");
var _iterator = require("../../core/utils/iterator");
var _type = require("../../core/utils/type");
var _position = require("../../core/utils/position");
var _translator = require("../../animation/translator");
var _class = _interopRequireDefault(require("../../core/class"));
var _animator = _interopRequireDefault(require("./animator"));
var _index = require("../../events/utils/index");
var _common = require("../../core/utils/common");
var _ui = _interopRequireDefault(require("./ui.scrollbar"));
var _deferred = require("../../core/utils/deferred");
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
}
}
const SCROLLABLE_SIMULATED = "dxSimulatedScrollable";
const SCROLLABLE_STRATEGY = "dxScrollableStrategy";
const SCROLLABLE_SIMULATED_CURSOR = "dxSimulatedScrollableCursor";
const SCROLLABLE_SIMULATED_KEYBOARD = "dxSimulatedScrollableKeyboard";
const SCROLLABLE_SIMULATED_CLASS = "dx-scrollable-simulated";
const SCROLLABLE_SCROLLBARS_ALWAYSVISIBLE = "dx-scrollable-scrollbars-alwaysvisible";
const SCROLLABLE_SCROLLBAR_CLASS = "dx-scrollable-scrollbar";
const VERTICAL = "vertical";
const HORIZONTAL = "horizontal";
const ACCELERATION = .92;
const OUT_BOUNDS_ACCELERATION = .5;
const MIN_VELOCITY_LIMIT = 1;
const FRAME_DURATION = Math.round(1e3 / 60);
const SCROLL_LINE_HEIGHT = 40;
const VALIDATE_WHEEL_TIMEOUT = 500;
const BOUNCE_MIN_VELOCITY_LIMIT = .2;
const BOUNCE_DURATION = 400;
const BOUNCE_FRAMES = 400 / FRAME_DURATION;
const BOUNCE_ACCELERATION_SUM = (1 - Math.pow(.92, BOUNCE_FRAMES)) / (1 - .92);
const KEY_CODES = {
PAGE_UP: "pageUp",
PAGE_DOWN: "pageDown",
END: "end",
HOME: "home",
LEFT: "leftArrow",
UP: "upArrow",
RIGHT: "rightArrow",
DOWN: "downArrow",
TAB: "tab"
};
const InertiaAnimator = _animator.default.inherit({
ctor: function(scroller) {
this.callBase();
this.scroller = scroller
},
VELOCITY_LIMIT: 1,
_isFinished: function() {
return Math.abs(this.scroller._velocity) <= this.VELOCITY_LIMIT
},
_step: function() {
this.scroller._scrollStep(this.scroller._velocity);
this.scroller._velocity *= this._acceleration()
},
_acceleration: function() {
return this.scroller._inBounds() ? .92 : .5
},
_complete: function() {
this.scroller._scrollComplete()
}
});
const BounceAnimator = InertiaAnimator.inherit({
VELOCITY_LIMIT: .2,
_isFinished: function() {
return this.scroller._crossBoundOnNextStep() || this.callBase()
},
_acceleration: function() {
return .92
},
_complete: function() {
this.scroller._move(this.scroller._bounceLocation);
this.callBase()
}
});
const Scroller = _class.default.inherit({
ctor: function(options) {
this._initOptions(options);
this._initAnimators();
this._initScrollbar()
},
_initOptions: function(options) {
this._location = 0;
this._topReached = false;
this._bottomReached = false;
this._axis = options.direction === HORIZONTAL ? "x" : "y";
this._prop = options.direction === HORIZONTAL ? "left" : "top";
this._dimension = options.direction === HORIZONTAL ? "width" : "height";
this._scrollProp = options.direction === HORIZONTAL ? "scrollLeft" : "scrollTop";
(0, _iterator.each)(options, (optionName, optionValue) => {
this["_" + optionName] = optionValue
})
},
_initAnimators: function() {
this._inertiaAnimator = new InertiaAnimator(this);
this._bounceAnimator = new BounceAnimator(this)
},
_initScrollbar: function() {
this._scrollbar = new _ui.default((0, _renderer.default)("<div>").appendTo(this._$container), {
direction: this._direction,
visible: this._scrollByThumb,
visibilityMode: this._visibilityModeNormalize(this._scrollbarVisible),
expandable: this._scrollByThumb
});
this._$scrollbar = this._scrollbar.$element()
},
_visibilityModeNormalize: function(mode) {
return true === mode ? "onScroll" : false === mode ? "never" : mode
},
_scrollStep: function(delta) {
const prevLocation = this._location;
this._location += delta;
this._suppressBounce();
this._move();
if (Math.abs(prevLocation - this._location) < 1) {
return
}
_events_engine.default.triggerHandler(this._$container, {
type: "scroll"
})
},
_suppressBounce: function() {
if (this._bounceEnabled || this._inBounds(this._location)) {
return
}
this._velocity = 0;
this._location = this._boundLocation()
},
_boundLocation: function(location) {
location = void 0 !== location ? location : this._location;
return Math.max(Math.min(location, this._maxOffset), this._minOffset)
},
_move: function(location) {
this._location = void 0 !== location ? location * this._getScaleRatio() : this._location;
this._moveContent();
this._moveScrollbar()
},
_moveContent: function() {
const location = this._location;
this._$container[this._scrollProp](-location / this._getScaleRatio());
this._moveContentByTranslator(location)
},
_getScaleRatio: function() {
if ((0, _window.hasWindow)() && !this._scaleRatio) {
const element = this._$element.get(0);
const realDimension = this._getRealDimension(element, this._dimension);
const baseDimension = this._getBaseDimension(element, this._dimension);
this._scaleRatio = Math.round(realDimension / baseDimension * 100) / 100
}
return this._scaleRatio || 1
},
_getRealDimension: function(element, dimension) {
return Math.round((0, _position.getBoundingRect)(element)[dimension])
},
_getBaseDimension: function(element, dimension) {
const dimensionName = "offset" + (0, _inflector.titleize)(dimension);
return element[dimensionName]
},
_moveContentByTranslator: function(location) {
let translateOffset;
const minOffset = -this._maxScrollPropValue;
if (location > 0) {
translateOffset = location
} else if (location <= minOffset) {
translateOffset = location - minOffset
} else {
translateOffset = location % 1
}
if (this._translateOffset === translateOffset) {
return
}
const targetLocation = {};
targetLocation[this._prop] = translateOffset;
this._translateOffset = translateOffset;
if (0 === translateOffset) {
(0, _translator.resetPosition)(this._$content);
return
}(0, _translator.move)(this._$content, targetLocation)
},
_moveScrollbar: function() {
this._scrollbar.moveTo(this._location)
},
_scrollComplete: function() {
if (this._inBounds()) {
this._hideScrollbar();
if (this._completeDeferred) {
this._completeDeferred.resolve()
}
}
this._scrollToBounds()
},
_scrollToBounds: function() {
if (this._inBounds()) {
return
}
this._bounceAction();
this._setupBounce();
this._bounceAnimator.start()
},
_setupBounce: function() {
const boundLocation = this._bounceLocation = this._boundLocation();
const bounceDistance = boundLocation - this._location;
this._velocity = bounceDistance / BOUNCE_ACCELERATION_SUM
},
_inBounds: function(location) {
location = void 0 !== location ? location : this._location;
return this._boundLocation(location) === location
},
_crossBoundOnNextStep: function() {
const location = this._location;
const nextLocation = location + this._velocity;
return location < this._minOffset && nextLocation >= this._minOffset || location > this._maxOffset && nextLocation <= this._maxOffset
},
_initHandler: function(e) {
this._stopScrolling();
this._prepareThumbScrolling(e)
},
_stopScrolling: (0, _common.deferRenderer)((function() {
this._hideScrollbar();
this._inertiaAnimator.stop();
this._bounceAnimator.stop()
})),
_prepareThumbScrolling: function(e) {
if ((0, _index.isDxMouseWheelEvent)(e.originalEvent)) {
return
}
const $target = (0, _renderer.default)(e.originalEvent.target);
const scrollbarClicked = this._isScrollbar($target);
if (scrollbarClicked) {
this._moveToMouseLocation(e)
}
this._thumbScrolling = scrollbarClicked || this._isThumb($target);
this._crossThumbScrolling = !this._thumbScrolling && this._isAnyThumbScrolling($target);
if (this._thumbScrolling) {
this._scrollbar.feedbackOn()
}
},
_isThumbScrollingHandler: function($target) {
return this._isThumb($target)
},
_moveToMouseLocation: function(e) {
const mouseLocation = e["page" + this._axis.toUpperCase()] - this._$element.offset()[this._prop];
const location = this._location + mouseLocation / this._containerToContentRatio() - (0, _size.getHeight)(this._$container) / 2;
this._scrollStep(-Math.round(location))
},
_startHandler: function() {
this._showScrollbar()
},
_moveHandler: function(delta) {
if (this._crossThumbScrolling) {
return
}
if (this._thumbScrolling) {
delta[this._axis] = -Math.round(delta[this._axis] / this._containerToContentRatio())
}
this._scrollBy(delta)
},
_scrollBy: function(delta) {
delta = delta[this._axis];
if (!this._inBounds()) {
delta *= .5
}
this._scrollStep(delta)
},
_scrollByHandler: function(delta) {
this._scrollBy(delta);
this._scrollComplete()
},
_containerToContentRatio: function() {
return this._scrollbar.containerToContentRatio()
},
_endHandler: function(velocity) {
this._completeDeferred = new _deferred.Deferred;
this._velocity = velocity[this._axis];
this._inertiaHandler();
this._resetThumbScrolling();
return this._completeDeferred.promise()
},
_inertiaHandler: function() {
this._suppressInertia();
this._inertiaAnimator.start()
},
_suppressInertia: function() {
if (!this._inertiaEnabled || this._thumbScrolling) {
this._velocity = 0
}
},
_resetThumbScrolling: function() {
this._thumbScrolling = false;
this._crossThumbScrolling = false
},
_stopHandler: function() {
if (this._thumbScrolling) {
this._scrollComplete()
}
this._resetThumbScrolling();
this._scrollToBounds()
},
_disposeHandler: function() {
this._stopScrolling();
this._$scrollbar.remove()
},
_updateHandler: function() {
this._update();
this._moveToBounds()
},
_update: function() {
this._stopScrolling();
return (0, _common.deferUpdate)(() => {
this._resetScaleRatio();
this._updateLocation();
this._updateBounds();
this._updateScrollbar();
(0, _common.deferRender)(() => {
this._moveScrollbar();
this._scrollbar.update()
})
})
},
_resetScaleRatio: function() {
this._scaleRatio = null
},
_updateLocation: function() {
this._location = ((0, _translator.locate)(this._$content)[this._prop] - this._$container[this._scrollProp]()) * this._getScaleRatio()
},
_updateBounds: function() {
this._maxOffset = this._getMaxOffset();
this._minOffset = this._getMinOffset()
},
_getMaxOffset: function() {
return 0
},
_getMinOffset: function() {
this._maxScrollPropValue = Math.max(this._contentSize() - this._containerSize(), 0);
return -this._maxScrollPropValue
},
_updateScrollbar: (0, _common.deferUpdater)((function() {
const containerSize = this._containerSize();
const contentSize = this._contentSize();
const baseContainerSize = this._getBaseDimension(this._$container.get(0), this._dimension);
const baseContentSize = this._getBaseDimension(this._$content.get(0), this._dimension);
(0, _common.deferRender)(() => {
this._scrollbar.option({
containerSize: containerSize,
contentSize: contentSize,
baseContainerSize: baseContainerSize,
baseContentSize: baseContentSize,
scaleRatio: this._getScaleRatio()
})
})
})),
_moveToBounds: (0, _common.deferRenderer)((0, _common.deferUpdater)((0, _common.deferRenderer)((function() {
const location = this._boundLocation();
const locationChanged = location !== this._location;
this._location = location;
this._move();
if (locationChanged) {
this._scrollAction()
}
})))),
_createActionsHandler: function(actions) {
this._scrollAction = actions.scroll;
this._bounceAction = actions.bounce
},
_showScrollbar: function() {
this._scrollbar.option("visible", true)
},
_hideScrollbar: function() {
this._scrollbar.option("visible", false)
},
_containerSize: function() {
return this._getRealDimension(this._$container.get(0), this._dimension)
},
_contentSize: function() {
const isOverflowHidden = "hidden" === this._$content.css("overflow" + this._axis.toUpperCase());
let contentSize = this._getRealDimension(this._$content.get(0), this._dimension);
if (!isOverflowHidden) {
const containerScrollSize = this._$content[0]["scroll" + (0, _inflector.titleize)(this._dimension)] * this._getScaleRatio();
contentSize = Math.max(containerScrollSize, contentSize)
}
return contentSize
},
_validateEvent: function(e) {
const $target = (0, _renderer.default)(e.originalEvent.target);
return this._isThumb($target) || this._isScrollbar($target)
},
_isThumb: function($element) {
return this._scrollByThumb && this._scrollbar.isThumb($element)
},
_isScrollbar: function($element) {
return this._scrollByThumb && $element && $element.is(this._$scrollbar)
},
_reachedMin: function() {
return Math.round(this._location - this._minOffset) <= 0
},
_reachedMax: function() {
return Math.round(this._location - this._maxOffset) >= 0
},
_cursorEnterHandler: function() {
this._resetScaleRatio();
this._updateScrollbar();
this._scrollbar.cursorEnter()
},
_cursorLeaveHandler: function() {
this._scrollbar.cursorLeave()
},
dispose: _common.noop
});
exports.Scroller = Scroller;
let hoveredScrollable;
let activeScrollable;
const SimulatedStrategy = _class.default.inherit({
ctor: function(scrollable) {
this._init(scrollable)
},
_init: function(scrollable) {
this._component = scrollable;
this._$element = scrollable.$element();
this._$container = (0, _renderer.default)(scrollable.container());
this._$wrapper = scrollable._$wrapper;
this._$content = scrollable.$content();
this.option = scrollable.option.bind(scrollable);
this._createActionByOption = scrollable._createActionByOption.bind(scrollable);
this._isLocked = scrollable._isLocked.bind(scrollable);
this._isDirection = scrollable._isDirection.bind(scrollable);
this._allowedDirection = scrollable._allowedDirection.bind(scrollable);
this._getMaxOffset = scrollable._getMaxOffset.bind(scrollable)
},
render: function() {
this._$element.addClass("dx-scrollable-simulated");
this._createScrollers();
if (this.option("useKeyboard")) {
this._$container.prop("tabIndex", 0)
}
this._attachKeyboardHandler();
this._attachCursorHandlers()
},
_createScrollers: function() {
this._scrollers = {};
if (this._isDirection(HORIZONTAL)) {
this._createScroller(HORIZONTAL)
}
if (this._isDirection(VERTICAL)) {
this._createScroller(VERTICAL)
}
this._$element.toggleClass(SCROLLABLE_SCROLLBARS_ALWAYSVISIBLE, "always" === this.option("showScrollbar"))
},
_createScroller: function(direction) {
this._scrollers[direction] = new Scroller(this._scrollerOptions(direction))
},
_scrollerOptions: function(direction) {
return {
direction: direction,
$content: this._$content,
$container: this._$container,
$wrapper: this._$wrapper,
$element: this._$element,
scrollByThumb: this.option("scrollByThumb"),
scrollbarVisible: this.option("showScrollbar"),
bounceEnabled: this.option("bounceEnabled"),
inertiaEnabled: this.option("inertiaEnabled"),
isAnyThumbScrolling: this._isAnyThumbScrolling.bind(this)
}
},
_applyScaleRatio: function(targetLocation) {
for (const direction in this._scrollers) {
const prop = this._getPropByDirection(direction);
if ((0, _type.isDefined)(targetLocation[prop])) {
const scroller = this._scrollers[direction];
targetLocation[prop] *= scroller._getScaleRatio()
}
}
return targetLocation
},
_isAnyThumbScrolling: function($target) {
let result = false;
this._eventHandler("isThumbScrolling", $target).done((function(isThumbScrollingVertical, isThumbScrollingHorizontal) {
result = isThumbScrollingVertical || isThumbScrollingHorizontal
}));
return result
},
handleInit: function(e) {
this._suppressDirections(e);
this._eventForUserAction = e;
this._eventHandler("init", e)
},
_suppressDirections: function(e) {
if ((0, _index.isDxMouseWheelEvent)(e.originalEvent)) {
this._prepareDirections(true);
return
}
this._prepareDirections();
this._eachScroller((function(scroller, direction) {
const $target = (0, _renderer.default)(e.originalEvent.target);
const isValid = scroller._validateEvent(e) || this.option("scrollByContent") && this._isContent($target);
this._validDirections[direction] = isValid
}))
},
_isContent: function($element) {
return !!$element.closest(this._$element).length
},
_prepareDirections: function(value) {
value = value || false;
this._validDirections = {};
this._validDirections.horizontal = value;
this._validDirections.vertical = value
},
_eachScroller: function(callback) {
callback = callback.bind(this);
(0, _iterator.each)(this._scrollers, (function(direction, scroller) {
callback(scroller, direction)
}))
},
handleStart: function(e) {
this._eventForUserAction = e;
this._eventHandler("start").done(this._startAction)
},
_saveActive: function() {
activeScrollable = this
},
_resetActive: function() {
if (activeScrollable === this) {
activeScrollable = null
}
},
handleMove: function(e) {
if (this._isLocked()) {
e.cancel = true;
this._resetActive();
return
}
this._saveActive();
e.preventDefault && e.preventDefault();
this._adjustDistance(e, e.delta);
this._eventForUserAction = e;
this._eventHandler("move", e.delta)
},
_adjustDistance: function(e, distance) {
distance.x *= this._validDirections.horizontal;
distance.y *= this._validDirections.vertical;
const devicePixelRatio = this._tryGetDevicePixelRatio();
if (devicePixelRatio && (0, _index.isDxMouseWheelEvent)(e.originalEvent)) {
distance.x = Math.round(distance.x / devicePixelRatio * 100) / 100;
distance.y = Math.round(distance.y / devicePixelRatio * 100) / 100
}
},
_tryGetDevicePixelRatio: function() {
if ((0, _window.hasWindow)()) {
return (0, _window.getWindow)().devicePixelRatio
}
},
handleEnd: function(e) {
this._resetActive();
this._refreshCursorState(e.originalEvent && e.originalEvent.target);
this._adjustDistance(e, e.velocity);
this._eventForUserAction = e;
return this._eventHandler("end", e.velocity).done(this._endAction)
},
handleCancel: function(e) {
this._resetActive();
this._eventForUserAction = e;
return this._eventHandler("end", {
x: 0,
y: 0
})
},
handleStop: function() {
this._resetActive();
this._eventHandler("stop")
},
handleScroll: function() {
this._updateRtlConfig();
this._scrollAction()
},
_attachKeyboardHandler: function() {
_events_engine.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_KEYBOARD));
if (!this.option("disabled") && this.option("useKeyboard")) {
_events_engine.default.on(this._$element, (0, _index.addNamespace)("keydown", SCROLLABLE_SIMULATED_KEYBOARD), this._keyDownHandler.bind(this))
}
},
_keyDownHandler: function(e) {
clearTimeout(this._updateHandlerTimeout);
this._updateHandlerTimeout = setTimeout(() => {
if ((0, _index.normalizeKeyName)(e) === KEY_CODES.TAB) {
this._eachScroller(scroller => {
scroller._updateHandler()
})
}
});
if (!this._$container.is(_dom_adapter.default.getActiveElement(this._$container.get(0)))) {
return
}
let handled = true;
switch ((0, _index.normalizeKeyName)(e)) {
case KEY_CODES.DOWN:
this._scrollByLine({
y: 1
});
break;
case KEY_CODES.UP:
this._scrollByLine({
y: -1
});
break;
case KEY_CODES.RIGHT:
this._scrollByLine({
x: 1
});
break;
case KEY_CODES.LEFT:
this._scrollByLine({
x: -1
});
break;
case KEY_CODES.PAGE_DOWN:
this._scrollByPage(1);
break;
case KEY_CODES.PAGE_UP:
this._scrollByPage(-1);
break;
case KEY_CODES.HOME:
this._scrollToHome();
break;
case KEY_CODES.END:
this._scrollToEnd();
break;
default:
handled = false
}
if (handled) {
e.stopPropagation();
e.preventDefault()
}
},
_scrollByLine: function(lines) {
const devicePixelRatio = this._tryGetDevicePixelRatio();
let scrollOffset = 40;
if (devicePixelRatio) {
scrollOffset = Math.abs(scrollOffset / devicePixelRatio * 100) / 100
}
this.scrollBy({
top: (lines.y || 0) * -scrollOffset,
left: (lines.x || 0) * -scrollOffset
})
},
_scrollByPage: function(page) {
const prop = this._wheelProp();
const dimension = this._dimensionByProp(prop);
const distance = {};
const getter = "width" === dimension ? _size.getWidth : _size.getHeight;
distance[prop] = page * -getter(this._$container);
this.scrollBy(distance)
},
_dimensionByProp: function(prop) {
return "left" === prop ? "width" : "height"
},
_getPropByDirection: function(direction) {
return direction === HORIZONTAL ? "left" : "top"
},
_scrollToHome: function() {
const prop = this._wheelProp();
const distance = {};
distance[prop] = 0;
this._component.scrollTo(distance)
},
_scrollToEnd: function() {
const prop = this._wheelProp();
const dimension = this._dimensionByProp(prop);
const distance = {};
const getter = "width" === dimension ? _size.getWidth : _size.getHeight;
distance[prop] = getter(this._$content) - getter(this._$container);
this._component.scrollTo(distance)
},
createActions: function() {
this._startAction = this._createActionHandler("onStart");
this._endAction = this._createActionHandler("onEnd");
this._updateAction = this._createActionHandler("onUpdated");
this._createScrollerActions()
},
_createScrollerActions: function() {
this._scrollAction = this._createActionHandler("onScroll");
this._bounceAction = this._createActionHandler("onBounce");
this._eventHandler("createActions", {
scroll: this._scrollAction,
bounce: this._bounceAction
})
},
_createActionHandler: function(optionName) {
const actionHandler = this._createActionByOption(optionName);
return () => {
actionHandler((0, _extend.extend)(this._createActionArgs(), arguments))
}
},
_createActionArgs: function() {
const {
horizontal: scrollerX,
vertical: scrollerY
} = this._scrollers;
const offset = this._getScrollOffset();
this._scrollOffset = {
top: scrollerY && offset.top,
left: scrollerX && offset.left
};
return {
event: this._eventForUserAction,
scrollOffset: this._scrollOffset,
reachedLeft: scrollerX && scrollerX._reachedMax(),
reachedRight: scrollerX && scrollerX._reachedMin(),
reachedTop: scrollerY && scrollerY._reachedMax(),
reachedBottom: scrollerY && scrollerY._reachedMin()
}
},
_getScrollOffset() {
return {
top: -this.location().top,
left: -this.location().left
}
},
_eventHandler: function(eventName) {
const args = [].slice.call(arguments).slice(1);
const deferreds = (0, _iterator.map)(this._scrollers, scroller => scroller["_" + eventName + "Handler"].apply(scroller, args));
return _deferred.when.apply(_renderer.default, deferreds).promise()
},
location: function() {
const location = (0, _translator.locate)(this._$content);
location.top -= this._$container.scrollTop();
location.left -= this._$container.scrollLeft();
return location
},
disabledChanged: function() {
this._attachCursorHandlers()
},
_attachCursorHandlers: function() {
_events_engine.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_CURSOR));
if (!this.option("disabled") && this._isHoverMode()) {
_events_engine.default.on(this._$element, (0, _index.addNamespace)("mouseenter", SCROLLABLE_SIMULATED_CURSOR), this._cursorEnterHandler.bind(this));
_events_engine.default.on(this._$element, (0, _index.addNamespace)("mouseleave", SCROLLABLE_SIMULATED_CURSOR), this._cursorLeaveHandler.bind(this))
}
},
_isHoverMode: function() {
return "onHover" === this.option("showScrollbar")
},
_cursorEnterHandler: function(e) {
e = e || {};
e.originalEvent = e.originalEvent || {};
if (activeScrollable || e.originalEvent._hoverHandled) {
return
}
if (hoveredScrollable) {
hoveredScrollable._cursorLeaveHandler()
}
hoveredScrollable = this;
this._eventHandler("cursorEnter");
e.originalEvent._hoverHandled = true
},
_cursorLeaveHandler: function(e) {
if (hoveredScrollable !== this || activeScrollable === hoveredScrollable) {
return
}
this._eventHandler("cursorLeave");
hoveredScrollable = null;
this._refreshCursorState(e && e.relatedTarget)
},
_refreshCursorState: function(target) {
if (!this._isHoverMode() && (!target || activeScrollable)) {
return
}
const $target = (0, _renderer.default)(target);
const $scrollable = $target.closest(".".concat("dx-scrollable-simulated", ":not(.dx-state-disabled)"));
const targetScrollable = $scrollable.length && $scrollable.data(SCROLLABLE_STRATEGY);
if (hoveredScrollable && hoveredScrollable !== targetScrollable) {
hoveredScrollable._cursorLeaveHandler()
}
if (targetScrollable) {
targetScrollable._cursorEnterHandler()
}
},
update: function() {
const result = this._eventHandler("update").done(this._updateAction);
return (0, _deferred.when)(result, (0, _common.deferUpdate)(() => {
const allowedDirections = this._allowedDirections();
(0, _common.deferRender)(() => {
let touchDirection = allowedDirections.vertical ? "pan-x" : "";
touchDirection = allowedDirections.horizontal ? "pan-y" : touchDirection;
touchDirection = allowedDirections.vertical && allowedDirections.horizontal ? "none" : touchDirection;
this._$container.css("touchAction", touchDirection)
});
return (0, _deferred.when)().promise()
}))
},
_allowedDirections: function() {
const bounceEnabled = this.option("bounceEnabled");
const verticalScroller = this._scrollers.vertical;
const horizontalScroller = this._scrollers.horizontal;
return {
vertical: verticalScroller && (verticalScroller._minOffset < 0 || bounceEnabled),
horizontal: horizontalScroller && (horizontalScroller._minOffset < 0 || bounceEnabled)
}
},
_updateBounds: function() {
this._scrollers.horizontal && this._scrollers.horizontal._updateBounds()
},
_isHorizontalAndRtlEnabled: function() {
return this.option("rtlEnabled") && this.option("direction") !== VERTICAL
},
updateRtlPosition: function(needInitializeRtlConfig) {
if (needInitializeRtlConfig) {
this._rtlConfig = {
scrollRight: 0,
clientWidth: this._$container.get(0).clientWidth,
windowPixelRatio: this._getWindowDevicePixelRatio()
}
}
this._updateBounds();
if (this._isHorizontalAndRtlEnabled()) {
let scrollLeft = this._getMaxOffset().left - this._rtlConfig.scrollRight;
if (scrollLeft <= 0) {
scrollLeft = 0;
this._rtlConfig.scrollRight = this._getMaxOffset().left
}
if (this._getScrollOffset().left !== scrollLeft) {
this._rtlConfig.skipUpdating = true;
this._component.scrollTo({
left: scrollLeft
});
this._rtlConfig.skipUpdating = false
}
}
},
_updateRtlConfig: function() {
if (this._isHorizontalAndRtlEnabled() && !this._rtlConfig.skipUpdating) {
const {
clientWidth: clientWidth,
scrollLeft: scrollLeft
} = this._$container.get(0);
const windowPixelRatio = this._getWindowDevicePixelRatio();
if (this._rtlConfig.windowPixelRatio === windowPixelRatio && this._rtlConfig.clientWidth === clientWidth) {
this._rtlConfig.scrollRight = this._getMaxOffset().left - scrollLeft
}
this._rtlConfig.clientWidth = clientWidth;
this._rtlConfig.windowPixelRatio = windowPixelRatio
}
},
_getWindowDevicePixelRatio: function() {
return (0, _window.hasWindow)() ? (0, _window.getWindow)().devicePixelRatio : 1
},
scrollBy: function(distance) {
const verticalScroller = this._scrollers.vertical;
const horizontalScroller = this._scrollers.horizontal;
if (verticalScroller) {
distance.top = verticalScroller._boundLocation(distance.top + verticalScroller._location) - verticalScroller._location
}
if (horizontalScroller) {
distance.left = horizontalScroller._boundLocation(distance.left + horizontalScroller._location) - horizontalScroller._location
}
this._prepareDirections(true);
this._startAction();
this._eventHandler("scrollBy", {
x: distance.left,
y: distance.top
});
this._endAction();
this._updateRtlConfig()
},
validate: function(e) {
if ((0, _index.isDxMouseWheelEvent)(e) && (0, _index.isCommandKeyPressed)(e)) {
return false
}
if (this.option("disabled")) {
return false
}
if (this.option("bounceEnabled")) {
return true
}
return (0, _index.isDxMouseWheelEvent)(e) ? this._validateWheel(e) : this._validateMove(e)
},
_validateWheel: function(e) {
const scroller = this._scrollers[this._wheelDirection(e)];
const reachedMin = scroller._reachedMin();
const reachedMax = scroller._reachedMax();
const contentGreaterThanContainer = !reachedMin || !reachedMax;
const locatedNotAtBound = !reachedMin && !reachedMax;
const scrollFromMin = reachedMin && e.delta > 0;
const scrollFromMax = reachedMax && e.delta < 0;
let validated = contentGreaterThanContainer && (locatedNotAtBound || scrollFromMin || scrollFromMax);
validated = validated || void 0 !== this._validateWheelTimer;
if (validated) {
clearTimeout(this._validateWheelTimer);
this._validateWheelTimer = setTimeout(() => {
this._validateWheelTimer = void 0
}, 500)
}
return validated
},
_validateMove: function(e) {
if (!this.option("scrollByContent") && !(0, _renderer.default)(e.target).closest(".".concat("dx-scrollable-scrollbar")).length) {
return false
}
return this._allowedDirection()
},
getDirection: function(e) {
return (0, _index.isDxMouseWheelEvent)(e) ? this._wheelDirection(e) : this._allowedDirection()
},
_wheelProp: function() {
return this._wheelDirection() === HORIZONTAL ? "left" : "top"
},
_wheelDirection: function(e) {
switch (this.option("direction")) {
case HORIZONTAL:
return HORIZONTAL;
case VERTICAL:
return VERTICAL;
default:
return e && e.shiftKey ? HORIZONTAL : VERTICAL
}
},
dispose: function() {
this._resetActive();
if (hoveredScrollable === this) {
hoveredScrollable = null
}
this._eventHandler("dispose");
this._detachEventHandlers();
this._$element.removeClass("dx-scrollable-simulated");
this._eventForUserAction = null;
clearTimeout(this._validateWheelTimer);
clearTimeout(this._updateHandlerTimeout)
},
_detachEventHandlers: function() {
_events_engine.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_CURSOR));
_events_engine.default.off(this._$container, ".".concat(SCROLLABLE_SIMULATED_KEYBOARD))
}
});
exports.SimulatedStrategy = SimulatedStrategy;