foundation.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /**
  2. * @license
  3. * Copyright 2018 Google Inc.
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. */
  23. import { __assign, __extends, __read } from "tslib";
  24. import { MDCFoundation } from '@material/base/foundation';
  25. import { cssClasses, strings } from './constants';
  26. import { MDCTabScrollerRTLDefault } from './rtl-default-scroller';
  27. import { MDCTabScrollerRTLNegative } from './rtl-negative-scroller';
  28. import { MDCTabScrollerRTLReverse } from './rtl-reverse-scroller';
  29. /** MDC Tab Scroller Foundation */
  30. var MDCTabScrollerFoundation = /** @class */ (function (_super) {
  31. __extends(MDCTabScrollerFoundation, _super);
  32. function MDCTabScrollerFoundation(adapter) {
  33. var _this = _super.call(this, __assign(__assign({}, MDCTabScrollerFoundation.defaultAdapter), adapter)) || this;
  34. /**
  35. * Controls whether we should handle the transitionend and interaction events
  36. * during the animation.
  37. */
  38. _this.isAnimating = false;
  39. return _this;
  40. }
  41. Object.defineProperty(MDCTabScrollerFoundation, "cssClasses", {
  42. get: function () {
  43. return cssClasses;
  44. },
  45. enumerable: false,
  46. configurable: true
  47. });
  48. Object.defineProperty(MDCTabScrollerFoundation, "strings", {
  49. get: function () {
  50. return strings;
  51. },
  52. enumerable: false,
  53. configurable: true
  54. });
  55. Object.defineProperty(MDCTabScrollerFoundation, "defaultAdapter", {
  56. get: function () {
  57. // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
  58. return {
  59. eventTargetMatchesSelector: function () { return false; },
  60. addClass: function () { return undefined; },
  61. removeClass: function () { return undefined; },
  62. addScrollAreaClass: function () { return undefined; },
  63. setScrollAreaStyleProperty: function () { return undefined; },
  64. setScrollContentStyleProperty: function () { return undefined; },
  65. getScrollContentStyleValue: function () { return ''; },
  66. setScrollAreaScrollLeft: function () { return undefined; },
  67. getScrollAreaScrollLeft: function () { return 0; },
  68. getScrollContentOffsetWidth: function () { return 0; },
  69. getScrollAreaOffsetWidth: function () { return 0; },
  70. computeScrollAreaClientRect: function () {
  71. return ({ top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 });
  72. },
  73. computeScrollContentClientRect: function () {
  74. return ({ top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 });
  75. },
  76. computeHorizontalScrollbarHeight: function () { return 0; },
  77. };
  78. // tslint:enable:object-literal-sort-keys
  79. },
  80. enumerable: false,
  81. configurable: true
  82. });
  83. MDCTabScrollerFoundation.prototype.init = function () {
  84. // Compute horizontal scrollbar height on scroller with overflow initially
  85. // hidden, then update overflow to scroll and immediately adjust bottom
  86. // margin to avoid the scrollbar initially appearing before JS runs.
  87. var horizontalScrollbarHeight = this.adapter.computeHorizontalScrollbarHeight();
  88. this.adapter.setScrollAreaStyleProperty('margin-bottom', -horizontalScrollbarHeight + 'px');
  89. this.adapter.addScrollAreaClass(MDCTabScrollerFoundation.cssClasses.SCROLL_AREA_SCROLL);
  90. };
  91. /**
  92. * Computes the current visual scroll position
  93. */
  94. MDCTabScrollerFoundation.prototype.getScrollPosition = function () {
  95. if (this.isRTL()) {
  96. return this.computeCurrentScrollPositionRTL();
  97. }
  98. var currentTranslateX = this.calculateCurrentTranslateX();
  99. var scrollLeft = this.adapter.getScrollAreaScrollLeft();
  100. return scrollLeft - currentTranslateX;
  101. };
  102. /**
  103. * Handles interaction events that occur during transition
  104. */
  105. MDCTabScrollerFoundation.prototype.handleInteraction = function () {
  106. // Early exit if we aren't animating
  107. if (!this.isAnimating) {
  108. return;
  109. }
  110. // Prevent other event listeners from handling this event
  111. this.stopScrollAnimation();
  112. };
  113. /**
  114. * Handles the transitionend event
  115. */
  116. MDCTabScrollerFoundation.prototype.handleTransitionEnd = function (evt) {
  117. // Early exit if we aren't animating or the event was triggered by a
  118. // different element.
  119. var evtTarget = evt.target;
  120. if (!this.isAnimating ||
  121. !this.adapter.eventTargetMatchesSelector(evtTarget, MDCTabScrollerFoundation.strings.CONTENT_SELECTOR)) {
  122. return;
  123. }
  124. this.isAnimating = false;
  125. this.adapter.removeClass(MDCTabScrollerFoundation.cssClasses.ANIMATING);
  126. };
  127. /**
  128. * Increment the scroll value by the scrollXIncrement using animation.
  129. * @param scrollXIncrement The value by which to increment the scroll position
  130. */
  131. MDCTabScrollerFoundation.prototype.incrementScroll = function (scrollXIncrement) {
  132. // Early exit for non-operational increment values
  133. if (scrollXIncrement === 0) {
  134. return;
  135. }
  136. this.animate(this.getIncrementScrollOperation(scrollXIncrement));
  137. };
  138. /**
  139. * Increment the scroll value by the scrollXIncrement without animation.
  140. * @param scrollXIncrement The value by which to increment the scroll position
  141. */
  142. MDCTabScrollerFoundation.prototype.incrementScrollImmediate = function (scrollXIncrement) {
  143. // Early exit for non-operational increment values
  144. if (scrollXIncrement === 0) {
  145. return;
  146. }
  147. var operation = this.getIncrementScrollOperation(scrollXIncrement);
  148. if (operation.scrollDelta === 0) {
  149. return;
  150. }
  151. this.stopScrollAnimation();
  152. this.adapter.setScrollAreaScrollLeft(operation.finalScrollPosition);
  153. };
  154. /**
  155. * Scrolls to the given scrollX value
  156. */
  157. MDCTabScrollerFoundation.prototype.scrollTo = function (scrollX) {
  158. if (this.isRTL()) {
  159. this.scrollToImplRTL(scrollX);
  160. return;
  161. }
  162. this.scrollToImpl(scrollX);
  163. };
  164. /**
  165. * @return Browser-specific {@link MDCTabScrollerRTL} instance.
  166. */
  167. MDCTabScrollerFoundation.prototype.getRTLScroller = function () {
  168. if (!this.rtlScrollerInstance) {
  169. this.rtlScrollerInstance = this.rtlScrollerFactory();
  170. }
  171. return this.rtlScrollerInstance;
  172. };
  173. /**
  174. * @return translateX value from a CSS matrix transform function string.
  175. */
  176. MDCTabScrollerFoundation.prototype.calculateCurrentTranslateX = function () {
  177. var transformValue = this.adapter.getScrollContentStyleValue('transform');
  178. // Early exit if no transform is present
  179. if (transformValue === 'none') {
  180. return 0;
  181. }
  182. // The transform value comes back as a matrix transformation in the form
  183. // of `matrix(a, b, c, d, tx, ty)`. We only care about tx (translateX) so
  184. // we're going to grab all the parenthesized values, strip out tx, and
  185. // parse it.
  186. var match = /\((.+?)\)/.exec(transformValue);
  187. if (!match) {
  188. return 0;
  189. }
  190. var matrixParams = match[1];
  191. // tslint:disable-next-line:ban-ts-suppressions "Unused vars" should be a linter warning, not a compiler error.
  192. // @ts-ignore These unused variables should retain their semantic names for
  193. // clarity.
  194. var _a = __read(matrixParams.split(','), 6), a = _a[0], b = _a[1], c = _a[2], d = _a[3], tx = _a[4], ty = _a[5];
  195. return parseFloat(tx); // tslint:disable-line:ban
  196. };
  197. /**
  198. * Calculates a safe scroll value that is > 0 and < the max scroll value
  199. * @param scrollX The distance to scroll
  200. */
  201. MDCTabScrollerFoundation.prototype.clampScrollValue = function (scrollX) {
  202. var edges = this.calculateScrollEdges();
  203. return Math.min(Math.max(edges.left, scrollX), edges.right);
  204. };
  205. MDCTabScrollerFoundation.prototype.computeCurrentScrollPositionRTL = function () {
  206. var translateX = this.calculateCurrentTranslateX();
  207. return this.getRTLScroller().getScrollPositionRTL(translateX);
  208. };
  209. MDCTabScrollerFoundation.prototype.calculateScrollEdges = function () {
  210. var contentWidth = this.adapter.getScrollContentOffsetWidth();
  211. var rootWidth = this.adapter.getScrollAreaOffsetWidth();
  212. return {
  213. left: 0,
  214. right: contentWidth - rootWidth,
  215. };
  216. };
  217. /**
  218. * Internal scroll method
  219. * @param scrollX The new scroll position
  220. */
  221. MDCTabScrollerFoundation.prototype.scrollToImpl = function (scrollX) {
  222. var currentScrollX = this.getScrollPosition();
  223. var safeScrollX = this.clampScrollValue(scrollX);
  224. var scrollDelta = safeScrollX - currentScrollX;
  225. this.animate({
  226. finalScrollPosition: safeScrollX,
  227. scrollDelta: scrollDelta,
  228. });
  229. };
  230. /**
  231. * Internal RTL scroll method
  232. * @param scrollX The new scroll position
  233. */
  234. MDCTabScrollerFoundation.prototype.scrollToImplRTL = function (scrollX) {
  235. var animation = this.getRTLScroller().scrollToRTL(scrollX);
  236. this.animate(animation);
  237. };
  238. /**
  239. * Internal method to compute the increment scroll operation values.
  240. * @param scrollX The desired scroll position increment
  241. * @return MDCTabScrollerAnimation with the sanitized values for performing
  242. * the scroll operation.
  243. */
  244. MDCTabScrollerFoundation.prototype.getIncrementScrollOperation = function (scrollX) {
  245. if (this.isRTL()) {
  246. return this.getRTLScroller().incrementScrollRTL(scrollX);
  247. }
  248. var currentScrollX = this.getScrollPosition();
  249. var targetScrollX = scrollX + currentScrollX;
  250. var safeScrollX = this.clampScrollValue(targetScrollX);
  251. var scrollDelta = safeScrollX - currentScrollX;
  252. return {
  253. finalScrollPosition: safeScrollX,
  254. scrollDelta: scrollDelta,
  255. };
  256. };
  257. /**
  258. * Animates the tab scrolling
  259. * @param animation The animation to apply
  260. */
  261. MDCTabScrollerFoundation.prototype.animate = function (animation) {
  262. var _this = this;
  263. // Early exit if translateX is 0, which means there's no animation to
  264. // perform
  265. if (animation.scrollDelta === 0) {
  266. return;
  267. }
  268. this.stopScrollAnimation();
  269. // This animation uses the FLIP approach.
  270. // Read more here: https://aerotwist.com/blog/flip-your-animations/
  271. this.adapter.setScrollAreaScrollLeft(animation.finalScrollPosition);
  272. this.adapter.setScrollContentStyleProperty('transform', "translateX(" + animation.scrollDelta + "px)");
  273. // Force repaint
  274. this.adapter.computeScrollAreaClientRect();
  275. requestAnimationFrame(function () {
  276. _this.adapter.addClass(MDCTabScrollerFoundation.cssClasses.ANIMATING);
  277. _this.adapter.setScrollContentStyleProperty('transform', 'none');
  278. });
  279. this.isAnimating = true;
  280. };
  281. /**
  282. * Stops scroll animation
  283. */
  284. MDCTabScrollerFoundation.prototype.stopScrollAnimation = function () {
  285. this.isAnimating = false;
  286. var currentScrollPosition = this.getAnimatingScrollPosition();
  287. this.adapter.removeClass(MDCTabScrollerFoundation.cssClasses.ANIMATING);
  288. this.adapter.setScrollContentStyleProperty('transform', 'translateX(0px)');
  289. this.adapter.setScrollAreaScrollLeft(currentScrollPosition);
  290. };
  291. /**
  292. * Gets the current scroll position during animation
  293. */
  294. MDCTabScrollerFoundation.prototype.getAnimatingScrollPosition = function () {
  295. var currentTranslateX = this.calculateCurrentTranslateX();
  296. var scrollLeft = this.adapter.getScrollAreaScrollLeft();
  297. if (this.isRTL()) {
  298. return this.getRTLScroller().getAnimatingScrollPosition(scrollLeft, currentTranslateX);
  299. }
  300. return scrollLeft - currentTranslateX;
  301. };
  302. /**
  303. * Determines the RTL Scroller to use
  304. */
  305. MDCTabScrollerFoundation.prototype.rtlScrollerFactory = function () {
  306. // Browsers have three different implementations of scrollLeft in RTL mode,
  307. // dependent on the browser. The behavior is based off the max LTR
  308. // scrollLeft value and 0.
  309. //
  310. // * Default scrolling in RTL *
  311. // - Left-most value: 0
  312. // - Right-most value: Max LTR scrollLeft value
  313. //
  314. // * Negative scrolling in RTL *
  315. // - Left-most value: Negated max LTR scrollLeft value
  316. // - Right-most value: 0
  317. //
  318. // * Reverse scrolling in RTL *
  319. // - Left-most value: Max LTR scrollLeft value
  320. // - Right-most value: 0
  321. //
  322. // We use those principles below to determine which RTL scrollLeft
  323. // behavior is implemented in the current browser.
  324. var initialScrollLeft = this.adapter.getScrollAreaScrollLeft();
  325. this.adapter.setScrollAreaScrollLeft(initialScrollLeft - 1);
  326. var newScrollLeft = this.adapter.getScrollAreaScrollLeft();
  327. // If the newScrollLeft value is negative,then we know that the browser has
  328. // implemented negative RTL scrolling, since all other implementations have
  329. // only positive values.
  330. if (newScrollLeft < 0) {
  331. // Undo the scrollLeft test check
  332. this.adapter.setScrollAreaScrollLeft(initialScrollLeft);
  333. return new MDCTabScrollerRTLNegative(this.adapter);
  334. }
  335. var rootClientRect = this.adapter.computeScrollAreaClientRect();
  336. var contentClientRect = this.adapter.computeScrollContentClientRect();
  337. var rightEdgeDelta = Math.round(contentClientRect.right - rootClientRect.right);
  338. // Undo the scrollLeft test check
  339. this.adapter.setScrollAreaScrollLeft(initialScrollLeft);
  340. // By calculating the clientRect of the root element and the clientRect of
  341. // the content element, we can determine how much the scroll value changed
  342. // when we performed the scrollLeft subtraction above.
  343. if (rightEdgeDelta === newScrollLeft) {
  344. return new MDCTabScrollerRTLReverse(this.adapter);
  345. }
  346. return new MDCTabScrollerRTLDefault(this.adapter);
  347. };
  348. MDCTabScrollerFoundation.prototype.isRTL = function () {
  349. return this.adapter.getScrollContentStyleValue('direction') === 'rtl';
  350. };
  351. return MDCTabScrollerFoundation;
  352. }(MDCFoundation));
  353. export { MDCTabScrollerFoundation };
  354. // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
  355. export default MDCTabScrollerFoundation;
  356. //# sourceMappingURL=foundation.js.map