devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
577 lines (572 loc) • 22.8 kB
JavaScript
/**
* DevExtreme (ui/scroll_view/ui.scrollable.js)
* Version: 20.1.7
* Build date: Tue Aug 25 2020
*
* Copyright (c) 2012 - 2020 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
if ("function" === typeof Symbol && "symbol" === typeof Symbol.iterator) {
_typeof = function(obj) {
return typeof obj
}
} else {
_typeof = function(obj) {
return obj && "function" === typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj
}
}
return _typeof(obj)
}
var _renderer = require("../../core/renderer");
var _renderer2 = _interopRequireDefault(_renderer);
var _events_engine = require("../../events/core/events_engine");
var _events_engine2 = _interopRequireDefault(_events_engine);
var _support = require("../../core/utils/support");
var _support2 = _interopRequireDefault(_support);
var _browser = require("../../core/utils/browser");
var _browser2 = _interopRequireDefault(_browser);
var _common = require("../../core/utils/common");
var _common2 = _interopRequireDefault(_common);
var _type = require("../../core/utils/type");
var _type2 = _interopRequireDefault(_type);
var _extend = require("../../core/utils/extend");
var _dom = require("../../core/utils/dom");
var _window = require("../../core/utils/window");
var _window2 = _interopRequireDefault(_window);
var _dom_adapter = require("../../core/dom_adapter");
var _dom_adapter2 = _interopRequireDefault(_dom_adapter);
var _devices = require("../../core/devices");
var _devices2 = _interopRequireDefault(_devices);
var _component_registrator = require("../../core/component_registrator");
var _component_registrator2 = _interopRequireDefault(_component_registrator);
var _dom_component = require("../../core/dom_component");
var _dom_component2 = _interopRequireDefault(_dom_component);
var _selectors = require("../widget/selectors");
var _selectors2 = _interopRequireDefault(_selectors);
var _utils = require("../../events/utils");
var eventUtils = _interopRequireWildcard(_utils);
var _uiEventsEmitterGesture = require("./ui.events.emitter.gesture.scroll");
var _uiEventsEmitterGesture2 = _interopRequireDefault(_uiEventsEmitterGesture);
var _uiScrollable = require("./ui.scrollable.simulated");
var _uiScrollable2 = _interopRequireDefault(_uiScrollable);
var _uiScrollable3 = require("./ui.scrollable.native");
var _uiScrollable4 = _interopRequireDefault(_uiScrollable3);
var _deferred = require("../../core/utils/deferred");
function _getRequireWildcardCache() {
if ("function" !== typeof WeakMap) {
return null
}
var cache = new WeakMap;
_getRequireWildcardCache = function() {
return cache
};
return cache
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj
}
if (null === obj || "object" !== _typeof(obj) && "function" !== typeof obj) {
return {
"default": obj
}
}
var cache = _getRequireWildcardCache();
if (cache && cache.has(obj)) {
return cache.get(obj)
}
var newObj = {};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc)
} else {
newObj[key] = obj[key]
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj)
}
return newObj
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
"default": obj
}
}
var SCROLLABLE = "dxScrollable";
var SCROLLABLE_STRATEGY = "dxScrollableStrategy";
var SCROLLABLE_CLASS = "dx-scrollable";
var SCROLLABLE_DISABLED_CLASS = "dx-scrollable-disabled";
var SCROLLABLE_CONTAINER_CLASS = "dx-scrollable-container";
var SCROLLABLE_WRAPPER_CLASS = "dx-scrollable-wrapper";
var SCROLLABLE_CONTENT_CLASS = "dx-scrollable-content";
var VERTICAL = "vertical";
var HORIZONTAL = "horizontal";
var BOTH = "both";
var deviceDependentOptions = function() {
return [{
device: function() {
return !_support2.default.nativeScrolling
},
options: {
useNative: false
}
}, {
device: function(_device) {
return !_devices2.default.isSimulator() && "desktop" === _devices2.default.real().deviceType && "generic" === _device.platform
},
options: {
bounceEnabled: false,
scrollByThumb: true,
scrollByContent: _support2.default.touch,
showScrollbar: "onHover"
}
}]
};
var Scrollable = _dom_component2.default.inherit({
_getDefaultOptions: function() {
return (0, _extend.extend)(this.callBase(), {
disabled: false,
onScroll: null,
direction: VERTICAL,
showScrollbar: "onScroll",
useNative: true,
bounceEnabled: true,
scrollByContent: true,
scrollByThumb: false,
onUpdated: null,
onStart: null,
onEnd: null,
onBounce: null,
onStop: null,
useSimulatedScrollbar: false,
useKeyboard: true,
inertiaEnabled: true,
pushBackValue: 0,
updateManually: false
})
},
_defaultOptionsRules: function() {
return this.callBase().concat(deviceDependentOptions(), [{
device: function() {
return _support2.default.nativeScrolling && "android" === _devices2.default.real().platform && !_browser2.default.mozilla
},
options: {
useSimulatedScrollbar: true
}
}, {
device: function() {
return "ios" === _devices2.default.real().platform
},
options: {
pushBackValue: 1
}
}])
},
_initOptions: function(options) {
this.callBase(options);
if (!("useSimulatedScrollbar" in options)) {
this._setUseSimulatedScrollbar()
}
},
_setUseSimulatedScrollbar: function() {
if (!this.initialOption("useSimulatedScrollbar")) {
this.option("useSimulatedScrollbar", !this.option("useNative"))
}
},
_init: function() {
this.callBase();
this._initScrollableMarkup();
this._locked = false
},
_visibilityChanged: function(visible) {
if (visible) {
this.update();
this._updateRtlPosition();
this._savedScrollOffset && this.scrollTo(this._savedScrollOffset);
delete this._savedScrollOffset
} else {
this._savedScrollOffset = this.scrollOffset()
}
},
_initScrollableMarkup: function() {
var $element = this.$element().addClass(SCROLLABLE_CLASS);
var $container = this._$container = (0, _renderer2.default)("<div>").addClass(SCROLLABLE_CONTAINER_CLASS);
var $wrapper = this._$wrapper = (0, _renderer2.default)("<div>").addClass(SCROLLABLE_WRAPPER_CLASS);
var $content = this._$content = (0, _renderer2.default)("<div>").addClass(SCROLLABLE_CONTENT_CLASS);
if (_dom_adapter2.default.hasDocumentProperty("onbeforeactivate") && _browser2.default.msie && _browser2.default.version < 12) {
_events_engine2.default.on($element, eventUtils.addNamespace("beforeactivate", SCROLLABLE), function(e) {
if (!(0, _renderer2.default)(e.target).is(_selectors2.default.focusable)) {
e.preventDefault()
}
})
}
$content.append($element.contents()).appendTo($container);
$container.appendTo($wrapper);
$wrapper.appendTo($element)
},
_dimensionChanged: function() {
this.update()
},
_initMarkup: function() {
this.callBase();
this._renderDirection()
},
_render: function() {
this._renderStrategy();
this._attachEventHandlers();
this._renderDisabledState();
this._createActions();
this.update();
this.callBase();
this._updateRtlPosition()
},
_updateRtlPosition: function() {
var _this = this;
this._updateBounds();
if (this.option("rtlEnabled") && this.option("direction") !== VERTICAL) {
_common2.default.deferUpdate(function() {
var containerElement = _this._container().get(0);
var maxLeftOffset = containerElement.scrollWidth - containerElement.clientWidth;
_common2.default.deferRender(function() {
_this.scrollTo({
left: maxLeftOffset
})
})
})
}
},
_updateBounds: function() {
this._strategy.updateBounds()
},
_attachEventHandlers: function() {
var strategy = this._strategy;
var initEventData = {
getDirection: strategy.getDirection.bind(strategy),
validate: this._validate.bind(this),
isNative: this.option("useNative"),
scrollTarget: this._$container
};
_events_engine2.default.off(this._$wrapper, "." + SCROLLABLE);
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.init, SCROLLABLE), initEventData, this._initHandler.bind(this));
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.start, SCROLLABLE), strategy.handleStart.bind(strategy));
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.move, SCROLLABLE), strategy.handleMove.bind(strategy));
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.end, SCROLLABLE), strategy.handleEnd.bind(strategy));
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.cancel, SCROLLABLE), strategy.handleCancel.bind(strategy));
_events_engine2.default.on(this._$wrapper, eventUtils.addNamespace(_uiEventsEmitterGesture2.default.stop, SCROLLABLE), strategy.handleStop.bind(strategy));
_events_engine2.default.off(this._$container, "." + SCROLLABLE);
_events_engine2.default.on(this._$container, eventUtils.addNamespace("scroll", SCROLLABLE), strategy.handleScroll.bind(strategy))
},
_validate: function(e) {
if (this._isLocked()) {
return false
}
this._updateIfNeed();
return this._strategy.validate(e)
},
_initHandler: function() {
var strategy = this._strategy;
strategy.handleInit.apply(strategy, arguments)
},
_renderDisabledState: function() {
this.$element().toggleClass(SCROLLABLE_DISABLED_CLASS, this.option("disabled"));
if (this.option("disabled")) {
this._lock()
} else {
this._unlock()
}
},
_renderDirection: function() {
this.$element().removeClass("dx-scrollable-" + HORIZONTAL).removeClass("dx-scrollable-" + VERTICAL).removeClass("dx-scrollable-" + BOTH).addClass("dx-scrollable-" + this.option("direction"))
},
_renderStrategy: function() {
this._createStrategy();
this._strategy.render();
this.$element().data(SCROLLABLE_STRATEGY, this._strategy)
},
_createStrategy: function() {
this._strategy = this.option("useNative") ? new _uiScrollable4.default(this) : new _uiScrollable2.default.SimulatedStrategy(this)
},
_createActions: function() {
this._strategy && this._strategy.createActions()
},
_clean: function() {
this._strategy && this._strategy.dispose()
},
_optionChanged: function(args) {
switch (args.name) {
case "onStart":
case "onEnd":
case "onStop":
case "onUpdated":
case "onScroll":
case "onBounce":
this._createActions();
break;
case "direction":
this._resetInactiveDirection();
this._invalidate();
break;
case "useNative":
this._setUseSimulatedScrollbar();
this._invalidate();
break;
case "inertiaEnabled":
case "scrollByContent":
case "scrollByThumb":
case "bounceEnabled":
case "useKeyboard":
case "showScrollbar":
case "useSimulatedScrollbar":
case "pushBackValue":
this._invalidate();
break;
case "disabled":
this._renderDisabledState();
this._strategy && this._strategy.disabledChanged();
break;
case "updateManually":
break;
case "width":
this.callBase(args);
this._updateRtlPosition();
break;
default:
this.callBase(args)
}
},
_resetInactiveDirection: function() {
var inactiveProp = this._getInactiveProp();
if (!inactiveProp || !_window2.default.hasWindow()) {
return
}
var scrollOffset = this.scrollOffset();
scrollOffset[inactiveProp] = 0;
this.scrollTo(scrollOffset)
},
_getInactiveProp: function() {
var direction = this.option("direction");
if (direction === VERTICAL) {
return "left"
}
if (direction === HORIZONTAL) {
return "top"
}
},
_location: function() {
return this._strategy.location()
},
_normalizeLocation: function(location) {
if (_type2.default.isPlainObject(location)) {
var left = _common2.default.ensureDefined(location.left, location.x);
var top = _common2.default.ensureDefined(location.top, location.y);
return {
left: _type2.default.isDefined(left) ? -left : void 0,
top: _type2.default.isDefined(top) ? -top : void 0
}
} else {
var direction = this.option("direction");
return {
left: direction !== VERTICAL ? -location : void 0,
top: direction !== HORIZONTAL ? -location : void 0
}
}
},
_isLocked: function() {
return this._locked
},
_lock: function() {
this._locked = true
},
_unlock: function() {
if (!this.option("disabled")) {
this._locked = false
}
},
_isDirection: function(direction) {
var current = this.option("direction");
if (direction === VERTICAL) {
return current !== HORIZONTAL
}
if (direction === HORIZONTAL) {
return current !== VERTICAL
}
return current === direction
},
_updateAllowedDirection: function() {
var allowedDirections = this._strategy._allowedDirections();
if (this._isDirection(BOTH) && allowedDirections.vertical && allowedDirections.horizontal) {
this._allowedDirectionValue = BOTH
} else {
if (this._isDirection(HORIZONTAL) && allowedDirections.horizontal) {
this._allowedDirectionValue = HORIZONTAL
} else {
if (this._isDirection(VERTICAL) && allowedDirections.vertical) {
this._allowedDirectionValue = VERTICAL
} else {
this._allowedDirectionValue = null
}
}
}
},
_allowedDirection: function() {
return this._allowedDirectionValue
},
_container: function() {
return this._$container
},
$content: function() {
return this._$content
},
content: function() {
return (0, _dom.getPublicElement)(this._$content)
},
scrollOffset: function() {
var location = this._location();
return {
top: -location.top,
left: -location.left
}
},
scrollTop: function() {
return this.scrollOffset().top
},
scrollLeft: function() {
return this.scrollOffset().left
},
clientHeight: function() {
return this._$container.height()
},
scrollHeight: function() {
return this.$content().outerHeight() - 2 * this._strategy.verticalOffset()
},
clientWidth: function() {
return this._$container.width()
},
scrollWidth: function() {
return this.$content().outerWidth()
},
update: function() {
if (!this._strategy) {
return
}
return (0, _deferred.when)(this._strategy.update()).done(function() {
this._updateAllowedDirection()
}.bind(this))
},
scrollBy: function(distance) {
distance = this._normalizeLocation(distance);
if (!distance.top && !distance.left) {
return
}
this._updateIfNeed();
this._strategy.scrollBy(distance)
},
scrollTo: function(targetLocation) {
targetLocation = this._normalizeLocation(targetLocation);
this._updateIfNeed();
var location = this._location();
if (!this.option("useNative")) {
targetLocation = this._strategy._applyScaleRatio(targetLocation);
location = this._strategy._applyScaleRatio(location)
}
var distance = this._normalizeLocation({
left: location.left - _common2.default.ensureDefined(targetLocation.left, location.left),
top: location.top - _common2.default.ensureDefined(targetLocation.top, location.top)
});
if (!distance.top && !distance.left) {
return
}
this._strategy.scrollBy(distance)
},
scrollToElement: function(element, offset) {
var $element = (0, _renderer2.default)(element);
var elementInsideContent = this.$content().find(element).length;
var elementIsInsideContent = $element.parents("." + SCROLLABLE_CLASS).length - $element.parents("." + SCROLLABLE_CONTENT_CLASS).length === 0;
if (!elementInsideContent || !elementIsInsideContent) {
return
}
var scrollPosition = {
top: 0,
left: 0
};
var direction = this.option("direction");
if (direction !== VERTICAL) {
scrollPosition.left = this.getScrollElementPosition($element, HORIZONTAL, offset)
}
if (direction !== HORIZONTAL) {
scrollPosition.top = this.getScrollElementPosition($element, VERTICAL, offset)
}
this.scrollTo(scrollPosition)
},
scrollToElementTopLeft: function(element) {
var $element = (0, _renderer2.default)(element);
var elementInsideContent = this.$content().find(element).length;
var elementIsInsideContent = $element.parents("." + SCROLLABLE_CLASS).length - $element.parents("." + SCROLLABLE_CONTENT_CLASS).length === 0;
if (!elementInsideContent || !elementIsInsideContent) {
return
}
var scrollPosition = {
top: 0,
left: 0
};
var direction = this.option("direction");
if (direction !== VERTICAL) {
var leftPosition = this._elementPositionRelativeToContent($element, "left");
scrollPosition.left = true === this.option("rtlEnabled") ? leftPosition + $element.width() - this.clientWidth() : leftPosition
}
if (direction !== HORIZONTAL) {
scrollPosition.top = this._elementPositionRelativeToContent($element, "top")
}
this.scrollTo(scrollPosition)
},
getScrollElementPosition: function($element, direction, offset) {
offset = offset || {};
var isVertical = direction === VERTICAL;
var startOffset = (isVertical ? offset.top : offset.left) || 0;
var endOffset = (isVertical ? offset.bottom : offset.right) || 0;
var pushBackOffset = isVertical ? this._strategy.verticalOffset() : 0;
var elementPositionRelativeToContent = this._elementPositionRelativeToContent($element, isVertical ? "top" : "left");
var elementPosition = elementPositionRelativeToContent - pushBackOffset;
var elementSize = $element[isVertical ? "outerHeight" : "outerWidth"]();
var scrollLocation = isVertical ? this.scrollTop() : this.scrollLeft();
var clientSize = isVertical ? this.clientHeight() : this.clientWidth();
var startDistance = scrollLocation - elementPosition + startOffset;
var endDistance = scrollLocation - elementPosition - elementSize + clientSize - endOffset;
if (startDistance <= 0 && endDistance >= 0) {
return scrollLocation
}
return scrollLocation - (Math.abs(startDistance) > Math.abs(endDistance) ? endDistance : startDistance)
},
_elementPositionRelativeToContent: function($element, prop) {
var result = 0;
while (this._hasScrollContent($element)) {
result += $element.position()[prop];
$element = $element.offsetParent()
}
return result
},
_hasScrollContent: function($element) {
var $content = this.$content();
return $element.closest($content).length && !$element.is($content)
},
_updateIfNeed: function() {
if (!this.option("updateManually")) {
this.update()
}
},
_useTemplates: function() {
return false
}
});
(0, _component_registrator2.default)(SCROLLABLE, Scrollable);
module.exports = Scrollable;
module.exports.deviceDependentOptions = deviceDependentOptions;