ui.scrollable.simulated.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. /**
  2. * DevExtreme (ui/scroll_view/ui.scrollable.simulated.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var _renderer = require("../../core/renderer");
  11. var _renderer2 = _interopRequireDefault(_renderer);
  12. var _dom_adapter = require("../../core/dom_adapter");
  13. var _dom_adapter2 = _interopRequireDefault(_dom_adapter);
  14. var _events_engine = require("../../events/core/events_engine");
  15. var _events_engine2 = _interopRequireDefault(_events_engine);
  16. var _inflector = require("../../core/utils/inflector");
  17. var _extend = require("../../core/utils/extend");
  18. var _window = require("../../core/utils/window");
  19. var _iterator = require("../../core/utils/iterator");
  20. var _type = require("../../core/utils/type");
  21. var _translator = require("../../animation/translator");
  22. var _translator2 = _interopRequireDefault(_translator);
  23. var _class = require("../../core/class");
  24. var _class2 = _interopRequireDefault(_class);
  25. var _animator = require("./animator");
  26. var _animator2 = _interopRequireDefault(_animator);
  27. var _devices = require("../../core/devices");
  28. var _devices2 = _interopRequireDefault(_devices);
  29. var _utils = require("../../events/utils");
  30. var _common = require("../../core/utils/common");
  31. var _ui = require("./ui.scrollbar");
  32. var _ui2 = _interopRequireDefault(_ui);
  33. var _deferred = require("../../core/utils/deferred");
  34. function _interopRequireDefault(obj) {
  35. return obj && obj.__esModule ? obj : {
  36. "default": obj
  37. }
  38. }
  39. var realDevice = _devices2.default.real;
  40. var isSluggishPlatform = "win" === realDevice.platform || "android" === realDevice.platform;
  41. var SCROLLABLE_SIMULATED = "dxSimulatedScrollable";
  42. var SCROLLABLE_STRATEGY = "dxScrollableStrategy";
  43. var SCROLLABLE_SIMULATED_CURSOR = SCROLLABLE_SIMULATED + "Cursor";
  44. var SCROLLABLE_SIMULATED_KEYBOARD = SCROLLABLE_SIMULATED + "Keyboard";
  45. var SCROLLABLE_SIMULATED_CLASS = "dx-scrollable-simulated";
  46. var SCROLLABLE_SCROLLBARS_HIDDEN = "dx-scrollable-scrollbars-hidden";
  47. var SCROLLABLE_SCROLLBARS_ALWAYSVISIBLE = "dx-scrollable-scrollbars-alwaysvisible";
  48. var SCROLLABLE_SCROLLBAR_CLASS = "dx-scrollable-scrollbar";
  49. var VERTICAL = "vertical";
  50. var HORIZONTAL = "horizontal";
  51. var ACCELERATION = isSluggishPlatform ? .95 : .92;
  52. var OUT_BOUNDS_ACCELERATION = .5;
  53. var MIN_VELOCITY_LIMIT = 1;
  54. var FRAME_DURATION = Math.round(1e3 / 60);
  55. var SCROLL_LINE_HEIGHT = 40;
  56. var VALIDATE_WHEEL_TIMEOUT = 500;
  57. var BOUNCE_MIN_VELOCITY_LIMIT = MIN_VELOCITY_LIMIT / 5;
  58. var BOUNCE_DURATION = isSluggishPlatform ? 300 : 400;
  59. var BOUNCE_FRAMES = BOUNCE_DURATION / FRAME_DURATION;
  60. var BOUNCE_ACCELERATION_SUM = (1 - Math.pow(ACCELERATION, BOUNCE_FRAMES)) / (1 - ACCELERATION);
  61. var KEY_CODES = {
  62. PAGE_UP: "pageUp",
  63. PAGE_DOWN: "pageDown",
  64. END: "end",
  65. HOME: "home",
  66. LEFT: "leftArrow",
  67. UP: "upArrow",
  68. RIGHT: "rightArrow",
  69. DOWN: "downArrow",
  70. TAB: "tab"
  71. };
  72. var InertiaAnimator = _animator2.default.inherit({
  73. ctor: function(scroller) {
  74. this.callBase();
  75. this.scroller = scroller
  76. },
  77. VELOCITY_LIMIT: MIN_VELOCITY_LIMIT,
  78. _isFinished: function() {
  79. return Math.abs(this.scroller._velocity) <= this.VELOCITY_LIMIT
  80. },
  81. _step: function() {
  82. this.scroller._scrollStep(this.scroller._velocity);
  83. this.scroller._velocity *= this._acceleration()
  84. },
  85. _acceleration: function() {
  86. return this.scroller._inBounds() ? ACCELERATION : OUT_BOUNDS_ACCELERATION
  87. },
  88. _complete: function() {
  89. this.scroller._scrollComplete()
  90. },
  91. _stop: function() {
  92. this.scroller._stopComplete()
  93. }
  94. });
  95. var BounceAnimator = InertiaAnimator.inherit({
  96. VELOCITY_LIMIT: BOUNCE_MIN_VELOCITY_LIMIT,
  97. _isFinished: function() {
  98. return this.scroller._crossBoundOnNextStep() || this.callBase()
  99. },
  100. _acceleration: function() {
  101. return ACCELERATION
  102. },
  103. _complete: function() {
  104. this.scroller._move(this.scroller._bounceLocation);
  105. this.callBase()
  106. }
  107. });
  108. var Scroller = _class2.default.inherit({
  109. ctor: function(options) {
  110. this._initOptions(options);
  111. this._initAnimators();
  112. this._initScrollbar()
  113. },
  114. _initOptions: function(options) {
  115. var _this = this;
  116. this._location = 0;
  117. this._topReached = false;
  118. this._bottomReached = false;
  119. this._axis = options.direction === HORIZONTAL ? "x" : "y";
  120. this._prop = options.direction === HORIZONTAL ? "left" : "top";
  121. this._dimension = options.direction === HORIZONTAL ? "width" : "height";
  122. this._scrollProp = options.direction === HORIZONTAL ? "scrollLeft" : "scrollTop";
  123. (0, _iterator.each)(options, function(optionName, optionValue) {
  124. _this["_" + optionName] = optionValue
  125. })
  126. },
  127. _initAnimators: function() {
  128. this._inertiaAnimator = new InertiaAnimator(this);
  129. this._bounceAnimator = new BounceAnimator(this)
  130. },
  131. _initScrollbar: function() {
  132. this._scrollbar = new _ui2.default((0, _renderer2.default)("<div>").appendTo(this._$container), {
  133. direction: this._direction,
  134. visible: this._scrollByThumb,
  135. visibilityMode: this._visibilityModeNormalize(this._scrollbarVisible),
  136. expandable: this._scrollByThumb
  137. });
  138. this._$scrollbar = this._scrollbar.$element()
  139. },
  140. _visibilityModeNormalize: function(mode) {
  141. return true === mode ? "onScroll" : false === mode ? "never" : mode
  142. },
  143. _scrollStep: function(delta) {
  144. var prevLocation = this._location;
  145. this._location += delta;
  146. this._suppressBounce();
  147. this._move();
  148. if (Math.abs(prevLocation - this._location) < 1) {
  149. return
  150. }
  151. _events_engine2.default.triggerHandler(this._$container, {
  152. type: "scroll"
  153. })
  154. },
  155. _suppressBounce: function() {
  156. if (this._bounceEnabled || this._inBounds(this._location)) {
  157. return
  158. }
  159. this._velocity = 0;
  160. this._location = this._boundLocation()
  161. },
  162. _boundLocation: function(location) {
  163. location = void 0 !== location ? location : this._location;
  164. return Math.max(Math.min(location, this._maxOffset), this._minOffset)
  165. },
  166. _move: function(location) {
  167. this._location = void 0 !== location ? location * this._getScaleRatio() : this._location;
  168. this._moveContent();
  169. this._moveScrollbar()
  170. },
  171. _moveContent: function() {
  172. var location = this._location;
  173. this._$container[this._scrollProp](-location / this._getScaleRatio());
  174. this._moveContentByTranslator(location)
  175. },
  176. _getScaleRatio: function() {
  177. if ((0, _window.hasWindow)() && !this._scaleRatio) {
  178. var element = this._$element.get(0);
  179. var realDimension = this._getRealDimension(element, this._dimension);
  180. var baseDimension = this._getBaseDimension(element, this._dimension);
  181. this._scaleRatio = Math.round(realDimension / baseDimension * 100) / 100
  182. }
  183. return this._scaleRatio || 1
  184. },
  185. _getRealDimension: function(element, dimension) {
  186. return Math.round(element.getBoundingClientRect()[dimension])
  187. },
  188. _getBaseDimension: function(element, dimension) {
  189. var dimensionName = "offset" + (0, _inflector.titleize)(dimension);
  190. return element[dimensionName]
  191. },
  192. _moveContentByTranslator: function(location) {
  193. var translateOffset;
  194. var minOffset = -this._maxScrollPropValue;
  195. if (location > 0) {
  196. translateOffset = location
  197. } else {
  198. if (location <= minOffset) {
  199. translateOffset = location - minOffset
  200. } else {
  201. translateOffset = location % 1
  202. }
  203. }
  204. if (this._translateOffset === translateOffset) {
  205. return
  206. }
  207. var targetLocation = {};
  208. targetLocation[this._prop] = translateOffset;
  209. this._translateOffset = translateOffset;
  210. if (0 === translateOffset) {
  211. _translator2.default.resetPosition(this._$content);
  212. return
  213. }
  214. _translator2.default.move(this._$content, targetLocation)
  215. },
  216. _moveScrollbar: function() {
  217. this._scrollbar.moveTo(this._location)
  218. },
  219. _scrollComplete: function() {
  220. if (this._inBounds()) {
  221. this._hideScrollbar();
  222. if (this._completeDeferred) {
  223. this._completeDeferred.resolve()
  224. }
  225. }
  226. this._scrollToBounds()
  227. },
  228. _scrollToBounds: function() {
  229. if (this._inBounds()) {
  230. return
  231. }
  232. this._bounceAction();
  233. this._setupBounce();
  234. this._bounceAnimator.start()
  235. },
  236. _setupBounce: function() {
  237. var boundLocation = this._bounceLocation = this._boundLocation();
  238. var bounceDistance = boundLocation - this._location;
  239. this._velocity = bounceDistance / BOUNCE_ACCELERATION_SUM
  240. },
  241. _inBounds: function(location) {
  242. location = void 0 !== location ? location : this._location;
  243. return this._boundLocation(location) === location
  244. },
  245. _crossBoundOnNextStep: function() {
  246. var location = this._location;
  247. var nextLocation = location + this._velocity;
  248. return location < this._minOffset && nextLocation >= this._minOffset || location > this._maxOffset && nextLocation <= this._maxOffset
  249. },
  250. _initHandler: function(e) {
  251. this._stopDeferred = new _deferred.Deferred;
  252. this._stopScrolling();
  253. this._prepareThumbScrolling(e);
  254. return this._stopDeferred.promise()
  255. },
  256. _stopScrolling: (0, _common.deferRenderer)(function() {
  257. this._hideScrollbar();
  258. this._inertiaAnimator.stop();
  259. this._bounceAnimator.stop()
  260. }),
  261. _prepareThumbScrolling: function(e) {
  262. if ((0, _utils.isDxMouseWheelEvent)(e.originalEvent)) {
  263. return
  264. }
  265. var $target = (0, _renderer2.default)(e.originalEvent.target);
  266. var scrollbarClicked = this._isScrollbar($target);
  267. if (scrollbarClicked) {
  268. this._moveToMouseLocation(e)
  269. }
  270. this._thumbScrolling = scrollbarClicked || this._isThumb($target);
  271. this._crossThumbScrolling = !this._thumbScrolling && this._isAnyThumbScrolling($target);
  272. if (this._thumbScrolling) {
  273. this._scrollbar.feedbackOn()
  274. }
  275. },
  276. _isThumbScrollingHandler: function($target) {
  277. return this._isThumb($target)
  278. },
  279. _moveToMouseLocation: function(e) {
  280. var mouseLocation = e["page" + this._axis.toUpperCase()] - this._$element.offset()[this._prop];
  281. var location = this._location + mouseLocation / this._containerToContentRatio() - this._$container.height() / 2;
  282. this._scrollStep(-Math.round(location))
  283. },
  284. _stopComplete: function() {
  285. if (this._stopDeferred) {
  286. this._stopDeferred.resolve()
  287. }
  288. },
  289. _startHandler: function() {
  290. this._showScrollbar()
  291. },
  292. _moveHandler: function(delta) {
  293. if (this._crossThumbScrolling) {
  294. return
  295. }
  296. if (this._thumbScrolling) {
  297. delta[this._axis] = -Math.round(delta[this._axis] / this._containerToContentRatio())
  298. }
  299. this._scrollBy(delta)
  300. },
  301. _scrollBy: function(delta) {
  302. delta = delta[this._axis];
  303. if (!this._inBounds()) {
  304. delta *= OUT_BOUNDS_ACCELERATION
  305. }
  306. this._scrollStep(delta)
  307. },
  308. _scrollByHandler: function(delta) {
  309. this._scrollBy(delta);
  310. this._scrollComplete()
  311. },
  312. _containerToContentRatio: function() {
  313. return this._scrollbar.containerToContentRatio()
  314. },
  315. _endHandler: function(velocity) {
  316. this._completeDeferred = new _deferred.Deferred;
  317. this._velocity = velocity[this._axis];
  318. this._inertiaHandler();
  319. this._resetThumbScrolling();
  320. return this._completeDeferred.promise()
  321. },
  322. _inertiaHandler: function() {
  323. this._suppressInertia();
  324. this._inertiaAnimator.start()
  325. },
  326. _suppressInertia: function() {
  327. if (!this._inertiaEnabled || this._thumbScrolling) {
  328. this._velocity = 0
  329. }
  330. },
  331. _resetThumbScrolling: function() {
  332. this._thumbScrolling = false;
  333. this._crossThumbScrolling = false
  334. },
  335. _stopHandler: function() {
  336. if (this._thumbScrolling) {
  337. this._scrollComplete()
  338. }
  339. this._resetThumbScrolling();
  340. this._scrollToBounds()
  341. },
  342. _disposeHandler: function() {
  343. this._stopScrolling();
  344. this._$scrollbar.remove()
  345. },
  346. _updateHandler: function() {
  347. this._update();
  348. this._moveToBounds()
  349. },
  350. _update: function() {
  351. var _this2 = this;
  352. this._stopScrolling();
  353. return (0, _common.deferUpdate)(function() {
  354. _this2._resetScaleRatio();
  355. _this2._updateLocation();
  356. _this2._updateBounds();
  357. _this2._updateScrollbar();
  358. (0, _common.deferRender)(function() {
  359. _this2._moveScrollbar();
  360. _this2._scrollbar.update()
  361. })
  362. })
  363. },
  364. _resetScaleRatio: function() {
  365. this._scaleRatio = null
  366. },
  367. _updateLocation: function() {
  368. this._location = (_translator2.default.locate(this._$content)[this._prop] - this._$container[this._scrollProp]()) * this._getScaleRatio()
  369. },
  370. _updateBounds: function() {
  371. this._maxOffset = Math.round(this._getMaxOffset());
  372. this._minOffset = Math.round(this._getMinOffset())
  373. },
  374. _getMaxOffset: function() {
  375. return 0
  376. },
  377. _getMinOffset: function() {
  378. this._maxScrollPropValue = Math.max(this._contentSize() - this._containerSize(), 0);
  379. return -this._maxScrollPropValue
  380. },
  381. _updateScrollbar: (0, _common.deferUpdater)(function() {
  382. var _this3 = this;
  383. var containerSize = this._containerSize();
  384. var contentSize = this._contentSize();
  385. var baseContainerSize = this._getBaseDimension(this._$container.get(0), this._dimension);
  386. var baseContentSize = this._getBaseDimension(this._$content.get(0), this._dimension);
  387. (0, _common.deferRender)(function() {
  388. _this3._scrollbar.option({
  389. containerSize: containerSize,
  390. contentSize: contentSize,
  391. baseContainerSize: baseContainerSize,
  392. baseContentSize: baseContentSize,
  393. scaleRatio: _this3._getScaleRatio()
  394. })
  395. })
  396. }),
  397. _moveToBounds: (0, _common.deferRenderer)((0, _common.deferUpdater)((0, _common.deferRenderer)(function() {
  398. var location = this._boundLocation();
  399. var locationChanged = location !== this._location;
  400. this._location = location;
  401. this._move();
  402. if (locationChanged) {
  403. this._scrollAction()
  404. }
  405. }))),
  406. _createActionsHandler: function(actions) {
  407. this._scrollAction = actions.scroll;
  408. this._bounceAction = actions.bounce
  409. },
  410. _showScrollbar: function() {
  411. this._scrollbar.option("visible", true)
  412. },
  413. _hideScrollbar: function() {
  414. this._scrollbar.option("visible", false)
  415. },
  416. _containerSize: function() {
  417. return this._getRealDimension(this._$container.get(0), this._dimension)
  418. },
  419. _contentSize: function() {
  420. var isOverflowHidden = "hidden" === this._$content.css("overflow" + this._axis.toUpperCase());
  421. var contentSize = this._getRealDimension(this._$content.get(0), this._dimension);
  422. if (!isOverflowHidden) {
  423. var containerScrollSize = this._$content[0]["scroll" + (0, _inflector.titleize)(this._dimension)] * this._getScaleRatio();
  424. contentSize = Math.max(containerScrollSize, contentSize)
  425. }
  426. return contentSize
  427. },
  428. _validateEvent: function(e) {
  429. var $target = (0, _renderer2.default)(e.originalEvent.target);
  430. return this._isThumb($target) || this._isScrollbar($target) || this._isContent($target)
  431. },
  432. _isThumb: function($element) {
  433. return this._scrollByThumb && this._scrollbar.isThumb($element)
  434. },
  435. _isScrollbar: function($element) {
  436. return this._scrollByThumb && $element && $element.is(this._$scrollbar)
  437. },
  438. _isContent: function($element) {
  439. return this._scrollByContent && !!$element.closest(this._$element).length
  440. },
  441. _reachedMin: function() {
  442. return this._location <= this._minOffset
  443. },
  444. _reachedMax: function() {
  445. return this._location >= this._maxOffset
  446. },
  447. _cursorEnterHandler: function() {
  448. this._resetScaleRatio();
  449. this._updateScrollbar();
  450. this._scrollbar.cursorEnter()
  451. },
  452. _cursorLeaveHandler: function() {
  453. this._scrollbar.cursorLeave()
  454. },
  455. dispose: _common.noop
  456. });
  457. var hoveredScrollable;
  458. var activeScrollable;
  459. var SimulatedStrategy = _class2.default.inherit({
  460. ctor: function(scrollable) {
  461. this._init(scrollable)
  462. },
  463. _init: function(scrollable) {
  464. this._component = scrollable;
  465. this._$element = scrollable.$element();
  466. this._$container = scrollable._$container;
  467. this._$wrapper = scrollable._$wrapper;
  468. this._$content = scrollable._$content;
  469. this.option = scrollable.option.bind(scrollable);
  470. this._createActionByOption = scrollable._createActionByOption.bind(scrollable);
  471. this._isLocked = scrollable._isLocked.bind(scrollable);
  472. this._isDirection = scrollable._isDirection.bind(scrollable);
  473. this._allowedDirection = scrollable._allowedDirection.bind(scrollable)
  474. },
  475. render: function() {
  476. this._$element.addClass(SCROLLABLE_SIMULATED_CLASS);
  477. this._createScrollers();
  478. if (this.option("useKeyboard")) {
  479. this._$container.prop("tabIndex", 0)
  480. }
  481. this._attachKeyboardHandler();
  482. this._attachCursorHandlers()
  483. },
  484. _createScrollers: function() {
  485. this._scrollers = {};
  486. if (this._isDirection(HORIZONTAL)) {
  487. this._createScroller(HORIZONTAL)
  488. }
  489. if (this._isDirection(VERTICAL)) {
  490. this._createScroller(VERTICAL)
  491. }
  492. this._$element.toggleClass(SCROLLABLE_SCROLLBARS_ALWAYSVISIBLE, "always" === this.option("showScrollbar"));
  493. this._$element.toggleClass(SCROLLABLE_SCROLLBARS_HIDDEN, !this.option("showScrollbar"))
  494. },
  495. _createScroller: function(direction) {
  496. this._scrollers[direction] = new Scroller(this._scrollerOptions(direction))
  497. },
  498. _scrollerOptions: function(direction) {
  499. return {
  500. direction: direction,
  501. $content: this._$content,
  502. $container: this._$container,
  503. $wrapper: this._$wrapper,
  504. $element: this._$element,
  505. scrollByContent: this.option("scrollByContent"),
  506. scrollByThumb: this.option("scrollByThumb"),
  507. scrollbarVisible: this.option("showScrollbar"),
  508. bounceEnabled: this.option("bounceEnabled"),
  509. inertiaEnabled: this.option("inertiaEnabled"),
  510. isAnyThumbScrolling: this._isAnyThumbScrolling.bind(this)
  511. }
  512. },
  513. _applyScaleRatio: function(targetLocation) {
  514. for (var direction in this._scrollers) {
  515. var prop = this._getPropByDirection(direction);
  516. if ((0, _type.isDefined)(targetLocation[prop])) {
  517. var scroller = this._scrollers[direction];
  518. targetLocation[prop] *= scroller._getScaleRatio()
  519. }
  520. }
  521. return targetLocation
  522. },
  523. _isAnyThumbScrolling: function($target) {
  524. var result = false;
  525. this._eventHandler("isThumbScrolling", $target).done(function(isThumbScrollingVertical, isThumbScrollingHorizontal) {
  526. result = isThumbScrollingVertical || isThumbScrollingHorizontal
  527. });
  528. return result
  529. },
  530. handleInit: function(e) {
  531. this._suppressDirections(e);
  532. this._eventForUserAction = e;
  533. this._eventHandler("init", e).done(this._stopAction)
  534. },
  535. _suppressDirections: function(e) {
  536. if ((0, _utils.isDxMouseWheelEvent)(e.originalEvent)) {
  537. this._prepareDirections(true);
  538. return
  539. }
  540. this._prepareDirections();
  541. this._eachScroller(function(scroller, direction) {
  542. var isValid = scroller._validateEvent(e);
  543. this._validDirections[direction] = isValid
  544. })
  545. },
  546. _prepareDirections: function(value) {
  547. value = value || false;
  548. this._validDirections = {};
  549. this._validDirections[HORIZONTAL] = value;
  550. this._validDirections[VERTICAL] = value
  551. },
  552. _eachScroller: function(callback) {
  553. callback = callback.bind(this);
  554. (0, _iterator.each)(this._scrollers, function(direction, scroller) {
  555. callback(scroller, direction)
  556. })
  557. },
  558. handleStart: function(e) {
  559. this._eventForUserAction = e;
  560. this._eventHandler("start").done(this._startAction)
  561. },
  562. _saveActive: function() {
  563. activeScrollable = this
  564. },
  565. _resetActive: function() {
  566. if (activeScrollable === this) {
  567. activeScrollable = null
  568. }
  569. },
  570. handleMove: function(e) {
  571. if (this._isLocked()) {
  572. e.cancel = true;
  573. this._resetActive();
  574. return
  575. }
  576. this._saveActive();
  577. e.preventDefault && e.preventDefault();
  578. this._adjustDistance(e, e.delta);
  579. this._eventForUserAction = e;
  580. this._eventHandler("move", e.delta)
  581. },
  582. _adjustDistance: function(e, distance) {
  583. distance.x *= this._validDirections[HORIZONTAL];
  584. distance.y *= this._validDirections[VERTICAL];
  585. var devicePixelRatio = this._tryGetDevicePixelRatio();
  586. if (devicePixelRatio && (0, _utils.isDxMouseWheelEvent)(e.originalEvent)) {
  587. distance.x = Math.round(distance.x / devicePixelRatio * 100) / 100;
  588. distance.y = Math.round(distance.y / devicePixelRatio * 100) / 100
  589. }
  590. },
  591. _tryGetDevicePixelRatio: function() {
  592. if ((0, _window.hasWindow)()) {
  593. return (0, _window.getWindow)().devicePixelRatio
  594. }
  595. },
  596. handleEnd: function(e) {
  597. this._resetActive();
  598. this._refreshCursorState(e.originalEvent && e.originalEvent.target);
  599. this._adjustDistance(e, e.velocity);
  600. this._eventForUserAction = e;
  601. return this._eventHandler("end", e.velocity).done(this._endAction)
  602. },
  603. handleCancel: function(e) {
  604. this._resetActive();
  605. this._eventForUserAction = e;
  606. return this._eventHandler("end", {
  607. x: 0,
  608. y: 0
  609. })
  610. },
  611. handleStop: function() {
  612. this._resetActive();
  613. this._eventHandler("stop")
  614. },
  615. handleScroll: function() {
  616. this._scrollAction()
  617. },
  618. _attachKeyboardHandler: function() {
  619. _events_engine2.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_KEYBOARD));
  620. if (!this.option("disabled") && this.option("useKeyboard")) {
  621. _events_engine2.default.on(this._$element, (0, _utils.addNamespace)("keydown", SCROLLABLE_SIMULATED_KEYBOARD), this._keyDownHandler.bind(this))
  622. }
  623. },
  624. _keyDownHandler: function(e) {
  625. var _this4 = this;
  626. clearTimeout(this._updateHandlerTimeout);
  627. this._updateHandlerTimeout = setTimeout(function() {
  628. if ((0, _utils.normalizeKeyName)(e) === KEY_CODES.TAB) {
  629. _this4._eachScroller(function(scroller) {
  630. scroller._updateHandler()
  631. })
  632. }
  633. });
  634. if (!this._$container.is(_dom_adapter2.default.getActiveElement())) {
  635. return
  636. }
  637. var handled = true;
  638. switch ((0, _utils.normalizeKeyName)(e)) {
  639. case KEY_CODES.DOWN:
  640. this._scrollByLine({
  641. y: 1
  642. });
  643. break;
  644. case KEY_CODES.UP:
  645. this._scrollByLine({
  646. y: -1
  647. });
  648. break;
  649. case KEY_CODES.RIGHT:
  650. this._scrollByLine({
  651. x: 1
  652. });
  653. break;
  654. case KEY_CODES.LEFT:
  655. this._scrollByLine({
  656. x: -1
  657. });
  658. break;
  659. case KEY_CODES.PAGE_DOWN:
  660. this._scrollByPage(1);
  661. break;
  662. case KEY_CODES.PAGE_UP:
  663. this._scrollByPage(-1);
  664. break;
  665. case KEY_CODES.HOME:
  666. this._scrollToHome();
  667. break;
  668. case KEY_CODES.END:
  669. this._scrollToEnd();
  670. break;
  671. default:
  672. handled = false
  673. }
  674. if (handled) {
  675. e.stopPropagation();
  676. e.preventDefault()
  677. }
  678. },
  679. _scrollByLine: function(lines) {
  680. var devicePixelRatio = this._tryGetDevicePixelRatio();
  681. var scrollOffset = SCROLL_LINE_HEIGHT;
  682. if (devicePixelRatio) {
  683. scrollOffset = Math.abs(scrollOffset / devicePixelRatio * 100) / 100
  684. }
  685. this.scrollBy({
  686. top: (lines.y || 0) * -scrollOffset,
  687. left: (lines.x || 0) * -scrollOffset
  688. })
  689. },
  690. _scrollByPage: function(page) {
  691. var prop = this._wheelProp();
  692. var dimension = this._dimensionByProp(prop);
  693. var distance = {};
  694. distance[prop] = page * -this._$container[dimension]();
  695. this.scrollBy(distance)
  696. },
  697. _dimensionByProp: function(prop) {
  698. return "left" === prop ? "width" : "height"
  699. },
  700. _getPropByDirection: function(direction) {
  701. return direction === HORIZONTAL ? "left" : "top"
  702. },
  703. _scrollToHome: function() {
  704. var prop = this._wheelProp();
  705. var distance = {};
  706. distance[prop] = 0;
  707. this._component.scrollTo(distance)
  708. },
  709. _scrollToEnd: function() {
  710. var prop = this._wheelProp();
  711. var dimension = this._dimensionByProp(prop);
  712. var distance = {};
  713. distance[prop] = this._$content[dimension]() - this._$container[dimension]();
  714. this._component.scrollTo(distance)
  715. },
  716. createActions: function() {
  717. this._startAction = this._createActionHandler("onStart");
  718. this._stopAction = this._createActionHandler("onStop");
  719. this._endAction = this._createActionHandler("onEnd");
  720. this._updateAction = this._createActionHandler("onUpdated");
  721. this._createScrollerActions()
  722. },
  723. _createScrollerActions: function() {
  724. this._scrollAction = this._createActionHandler("onScroll");
  725. this._bounceAction = this._createActionHandler("onBounce");
  726. this._eventHandler("createActions", {
  727. scroll: this._scrollAction,
  728. bounce: this._bounceAction
  729. })
  730. },
  731. _createActionHandler: function(optionName) {
  732. var _arguments = arguments,
  733. _this5 = this;
  734. var actionHandler = this._createActionByOption(optionName);
  735. return function() {
  736. actionHandler((0, _extend.extend)(_this5._createActionArgs(), _arguments))
  737. }
  738. },
  739. _createActionArgs: function() {
  740. var scrollerX = this._scrollers[HORIZONTAL];
  741. var scrollerY = this._scrollers[VERTICAL];
  742. var location = this.location();
  743. this._scrollOffset = {
  744. top: scrollerY && -location.top,
  745. left: scrollerX && -location.left
  746. };
  747. return {
  748. event: this._eventForUserAction,
  749. scrollOffset: this._scrollOffset,
  750. reachedLeft: scrollerX && scrollerX._reachedMax(),
  751. reachedRight: scrollerX && scrollerX._reachedMin(),
  752. reachedTop: scrollerY && scrollerY._reachedMax(),
  753. reachedBottom: scrollerY && scrollerY._reachedMin()
  754. }
  755. },
  756. _eventHandler: function(eventName) {
  757. var args = [].slice.call(arguments).slice(1);
  758. var deferreds = (0, _iterator.map)(this._scrollers, function(scroller) {
  759. return scroller["_" + eventName + "Handler"].apply(scroller, args)
  760. });
  761. return _deferred.when.apply(_renderer2.default, deferreds).promise()
  762. },
  763. location: function location() {
  764. var location = _translator2.default.locate(this._$content);
  765. location.top -= this._$container.scrollTop();
  766. location.left -= this._$container.scrollLeft();
  767. return location
  768. },
  769. disabledChanged: function() {
  770. this._attachCursorHandlers()
  771. },
  772. _attachCursorHandlers: function() {
  773. _events_engine2.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_CURSOR));
  774. if (!this.option("disabled") && this._isHoverMode()) {
  775. _events_engine2.default.on(this._$element, (0, _utils.addNamespace)("mouseenter", SCROLLABLE_SIMULATED_CURSOR), this._cursorEnterHandler.bind(this));
  776. _events_engine2.default.on(this._$element, (0, _utils.addNamespace)("mouseleave", SCROLLABLE_SIMULATED_CURSOR), this._cursorLeaveHandler.bind(this))
  777. }
  778. },
  779. _isHoverMode: function() {
  780. return "onHover" === this.option("showScrollbar")
  781. },
  782. _cursorEnterHandler: function(e) {
  783. e = e || {};
  784. e.originalEvent = e.originalEvent || {};
  785. if (activeScrollable || e.originalEvent._hoverHandled) {
  786. return
  787. }
  788. if (hoveredScrollable) {
  789. hoveredScrollable._cursorLeaveHandler()
  790. }
  791. hoveredScrollable = this;
  792. this._eventHandler("cursorEnter");
  793. e.originalEvent._hoverHandled = true
  794. },
  795. _cursorLeaveHandler: function(e) {
  796. if (hoveredScrollable !== this || activeScrollable === hoveredScrollable) {
  797. return
  798. }
  799. this._eventHandler("cursorLeave");
  800. hoveredScrollable = null;
  801. this._refreshCursorState(e && e.relatedTarget)
  802. },
  803. _refreshCursorState: function(target) {
  804. if (!this._isHoverMode() && (!target || activeScrollable)) {
  805. return
  806. }
  807. var $target = (0, _renderer2.default)(target);
  808. var $scrollable = $target.closest(".".concat(SCROLLABLE_SIMULATED_CLASS, ":not(.dx-state-disabled)"));
  809. var targetScrollable = $scrollable.length && $scrollable.data(SCROLLABLE_STRATEGY);
  810. if (hoveredScrollable && hoveredScrollable !== targetScrollable) {
  811. hoveredScrollable._cursorLeaveHandler()
  812. }
  813. if (targetScrollable) {
  814. targetScrollable._cursorEnterHandler()
  815. }
  816. },
  817. update: function() {
  818. var _this6 = this;
  819. var result = this._eventHandler("update").done(this._updateAction);
  820. return (0, _deferred.when)(result, (0, _common.deferUpdate)(function() {
  821. var allowedDirections = _this6._allowedDirections();
  822. (0, _common.deferRender)(function() {
  823. var touchDirection = allowedDirections.vertical ? "pan-x" : "";
  824. touchDirection = allowedDirections.horizontal ? "pan-y" : touchDirection;
  825. touchDirection = allowedDirections.vertical && allowedDirections.horizontal ? "none" : touchDirection;
  826. _this6._$container.css("touchAction", touchDirection)
  827. });
  828. return (0, _deferred.when)().promise()
  829. }))
  830. },
  831. _allowedDirections: function() {
  832. var bounceEnabled = this.option("bounceEnabled");
  833. var verticalScroller = this._scrollers[VERTICAL];
  834. var horizontalScroller = this._scrollers[HORIZONTAL];
  835. return {
  836. vertical: verticalScroller && (verticalScroller._minOffset < 0 || bounceEnabled),
  837. horizontal: horizontalScroller && (horizontalScroller._minOffset < 0 || bounceEnabled)
  838. }
  839. },
  840. updateBounds: function() {
  841. this._scrollers[HORIZONTAL] && this._scrollers[HORIZONTAL]._updateBounds()
  842. },
  843. scrollBy: function(distance) {
  844. var verticalScroller = this._scrollers[VERTICAL];
  845. var horizontalScroller = this._scrollers[HORIZONTAL];
  846. if (verticalScroller) {
  847. distance.top = verticalScroller._boundLocation(distance.top + verticalScroller._location) - verticalScroller._location
  848. }
  849. if (horizontalScroller) {
  850. distance.left = horizontalScroller._boundLocation(distance.left + horizontalScroller._location) - horizontalScroller._location
  851. }
  852. this._prepareDirections(true);
  853. this._startAction();
  854. this._eventHandler("scrollBy", {
  855. x: distance.left,
  856. y: distance.top
  857. });
  858. this._endAction()
  859. },
  860. validate: function(e) {
  861. if (this.option("disabled")) {
  862. return false
  863. }
  864. if (this.option("bounceEnabled")) {
  865. return true
  866. }
  867. return (0, _utils.isDxMouseWheelEvent)(e) ? this._validateWheel(e) : this._validateMove(e)
  868. },
  869. _validateWheel: function(e) {
  870. var _this7 = this;
  871. var scroller = this._scrollers[this._wheelDirection(e)];
  872. var reachedMin = scroller._reachedMin();
  873. var reachedMax = scroller._reachedMax();
  874. var contentGreaterThanContainer = !reachedMin || !reachedMax;
  875. var locatedNotAtBound = !reachedMin && !reachedMax;
  876. var scrollFromMin = reachedMin && e.delta > 0;
  877. var scrollFromMax = reachedMax && e.delta < 0;
  878. var validated = contentGreaterThanContainer && (locatedNotAtBound || scrollFromMin || scrollFromMax);
  879. validated = validated || void 0 !== this._validateWheelTimer;
  880. if (validated) {
  881. clearTimeout(this._validateWheelTimer);
  882. this._validateWheelTimer = setTimeout(function() {
  883. _this7._validateWheelTimer = void 0
  884. }, VALIDATE_WHEEL_TIMEOUT)
  885. }
  886. return validated
  887. },
  888. _validateMove: function(e) {
  889. if (!this.option("scrollByContent") && !(0, _renderer2.default)(e.target).closest(".".concat(SCROLLABLE_SCROLLBAR_CLASS)).length) {
  890. return false
  891. }
  892. return this._allowedDirection()
  893. },
  894. getDirection: function(e) {
  895. return (0, _utils.isDxMouseWheelEvent)(e) ? this._wheelDirection(e) : this._allowedDirection()
  896. },
  897. _wheelProp: function() {
  898. return this._wheelDirection() === HORIZONTAL ? "left" : "top"
  899. },
  900. _wheelDirection: function(e) {
  901. switch (this.option("direction")) {
  902. case HORIZONTAL:
  903. return HORIZONTAL;
  904. case VERTICAL:
  905. return VERTICAL;
  906. default:
  907. return e && e.shiftKey ? HORIZONTAL : VERTICAL
  908. }
  909. },
  910. verticalOffset: function() {
  911. return 0
  912. },
  913. dispose: function() {
  914. this._resetActive();
  915. if (hoveredScrollable === this) {
  916. hoveredScrollable = null
  917. }
  918. this._eventHandler("dispose");
  919. this._detachEventHandlers();
  920. this._$element.removeClass(SCROLLABLE_SIMULATED_CLASS);
  921. this._eventForUserAction = null;
  922. clearTimeout(this._validateWheelTimer);
  923. clearTimeout(this._updateHandlerTimeout)
  924. },
  925. _detachEventHandlers: function() {
  926. _events_engine2.default.off(this._$element, ".".concat(SCROLLABLE_SIMULATED_CURSOR));
  927. _events_engine2.default.off(this._$container, ".".concat(SCROLLABLE_SIMULATED_KEYBOARD))
  928. }
  929. });
  930. exports.SimulatedStrategy = SimulatedStrategy;
  931. exports.Scroller = Scroller;