/**
 * DevExtreme (cjs/ui/scroll_view/ui.scrollable.simulated.js)
 * Version: 23.2.6 (build 24140-0112)
 * Build date: Sun May 19 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;
