foundation.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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 } from "tslib";
  24. import { MDCFoundation } from '@material/base/foundation';
  25. import { numbers, strings } from './constants';
  26. var ACCEPTABLE_KEYS = new Set();
  27. // IE11 has no support for new Set with iterable so we need to initialize this
  28. // by hand
  29. ACCEPTABLE_KEYS.add(strings.ARROW_LEFT_KEY);
  30. ACCEPTABLE_KEYS.add(strings.ARROW_RIGHT_KEY);
  31. ACCEPTABLE_KEYS.add(strings.END_KEY);
  32. ACCEPTABLE_KEYS.add(strings.HOME_KEY);
  33. ACCEPTABLE_KEYS.add(strings.ENTER_KEY);
  34. ACCEPTABLE_KEYS.add(strings.SPACE_KEY);
  35. var KEYCODE_MAP = new Map();
  36. // IE11 has no support for new Map with iterable so we need to initialize this
  37. // by hand
  38. KEYCODE_MAP.set(numbers.ARROW_LEFT_KEYCODE, strings.ARROW_LEFT_KEY);
  39. KEYCODE_MAP.set(numbers.ARROW_RIGHT_KEYCODE, strings.ARROW_RIGHT_KEY);
  40. KEYCODE_MAP.set(numbers.END_KEYCODE, strings.END_KEY);
  41. KEYCODE_MAP.set(numbers.HOME_KEYCODE, strings.HOME_KEY);
  42. KEYCODE_MAP.set(numbers.ENTER_KEYCODE, strings.ENTER_KEY);
  43. KEYCODE_MAP.set(numbers.SPACE_KEYCODE, strings.SPACE_KEY);
  44. /** MDC Tab Bar Foundation */
  45. var MDCTabBarFoundation = /** @class */ (function (_super) {
  46. __extends(MDCTabBarFoundation, _super);
  47. function MDCTabBarFoundation(adapter) {
  48. var _this = _super.call(this, __assign(__assign({}, MDCTabBarFoundation.defaultAdapter), adapter)) || this;
  49. _this.useAutomaticActivation = false;
  50. return _this;
  51. }
  52. Object.defineProperty(MDCTabBarFoundation, "strings", {
  53. get: function () {
  54. return strings;
  55. },
  56. enumerable: false,
  57. configurable: true
  58. });
  59. Object.defineProperty(MDCTabBarFoundation, "numbers", {
  60. get: function () {
  61. return numbers;
  62. },
  63. enumerable: false,
  64. configurable: true
  65. });
  66. Object.defineProperty(MDCTabBarFoundation, "defaultAdapter", {
  67. get: function () {
  68. // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
  69. return {
  70. scrollTo: function () { return undefined; },
  71. incrementScroll: function () { return undefined; },
  72. getScrollPosition: function () { return 0; },
  73. getScrollContentWidth: function () { return 0; },
  74. getOffsetWidth: function () { return 0; },
  75. isRTL: function () { return false; },
  76. setActiveTab: function () { return undefined; },
  77. activateTabAtIndex: function () { return undefined; },
  78. deactivateTabAtIndex: function () { return undefined; },
  79. focusTabAtIndex: function () { return undefined; },
  80. getTabIndicatorClientRectAtIndex: function () {
  81. return ({ top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 });
  82. },
  83. getTabDimensionsAtIndex: function () {
  84. return ({ rootLeft: 0, rootRight: 0, contentLeft: 0, contentRight: 0 });
  85. },
  86. getPreviousActiveTabIndex: function () { return -1; },
  87. getFocusedTabIndex: function () { return -1; },
  88. getIndexOfTabById: function () { return -1; },
  89. getTabListLength: function () { return 0; },
  90. notifyTabActivated: function () { return undefined; },
  91. };
  92. // tslint:enable:object-literal-sort-keys
  93. },
  94. enumerable: false,
  95. configurable: true
  96. });
  97. /**
  98. * Switches between automatic and manual activation modes.
  99. * See https://www.w3.org/TR/wai-aria-practices/#tabpanel for examples.
  100. */
  101. MDCTabBarFoundation.prototype.setUseAutomaticActivation = function (useAutomaticActivation) {
  102. this.useAutomaticActivation = useAutomaticActivation;
  103. };
  104. MDCTabBarFoundation.prototype.activateTab = function (index) {
  105. var previousActiveIndex = this.adapter.getPreviousActiveTabIndex();
  106. if (!this.indexIsInRange(index) || index === previousActiveIndex) {
  107. return;
  108. }
  109. var previousClientRect;
  110. if (previousActiveIndex !== -1) {
  111. this.adapter.deactivateTabAtIndex(previousActiveIndex);
  112. previousClientRect =
  113. this.adapter.getTabIndicatorClientRectAtIndex(previousActiveIndex);
  114. }
  115. this.adapter.activateTabAtIndex(index, previousClientRect);
  116. this.scrollIntoView(index);
  117. this.adapter.notifyTabActivated(index);
  118. };
  119. MDCTabBarFoundation.prototype.handleKeyDown = function (evt) {
  120. // Get the key from the event
  121. var key = this.getKeyFromEvent(evt);
  122. // Early exit if the event key isn't one of the keyboard navigation keys
  123. if (key === undefined) {
  124. return;
  125. }
  126. // Prevent default behavior for movement keys, but not for activation keys,
  127. // since :active is used to apply ripple
  128. if (!this.isActivationKey(key)) {
  129. evt.preventDefault();
  130. }
  131. if (this.useAutomaticActivation && this.isActivationKey(key)) {
  132. return;
  133. }
  134. var focusedTabIndex = this.adapter.getFocusedTabIndex();
  135. if (this.isActivationKey(key)) {
  136. this.adapter.setActiveTab(focusedTabIndex);
  137. return;
  138. }
  139. var index = this.determineTargetFromKey(focusedTabIndex, key);
  140. this.adapter.focusTabAtIndex(index);
  141. this.scrollIntoView(index);
  142. if (this.useAutomaticActivation) {
  143. this.adapter.setActiveTab(index);
  144. }
  145. };
  146. /**
  147. * Handles the MDCTab:interacted event
  148. */
  149. MDCTabBarFoundation.prototype.handleTabInteraction = function (evt) {
  150. this.adapter.setActiveTab(this.adapter.getIndexOfTabById(evt.detail.tabId));
  151. };
  152. /**
  153. * Scrolls the tab at the given index into view
  154. * @param index The tab index to make visible
  155. */
  156. MDCTabBarFoundation.prototype.scrollIntoView = function (index) {
  157. // Early exit if the index is out of range
  158. if (!this.indexIsInRange(index)) {
  159. return;
  160. }
  161. // Always scroll to 0 if scrolling to the 0th index
  162. if (index === 0) {
  163. this.adapter.scrollTo(0);
  164. return;
  165. }
  166. // Always scroll to the max value if scrolling to the Nth index
  167. // MDCTabScroller.scrollTo() will never scroll past the max possible value
  168. if (index === this.adapter.getTabListLength() - 1) {
  169. this.adapter.scrollTo(this.adapter.getScrollContentWidth());
  170. return;
  171. }
  172. if (this.isRTL()) {
  173. this.scrollIntoViewImplRTL(index);
  174. return;
  175. }
  176. this.scrollIntoViewImpl(index);
  177. };
  178. /**
  179. * Private method for determining the index of the destination tab based on
  180. * what key was pressed
  181. * @param origin The original index from which to determine the destination
  182. * @param key The name of the key
  183. */
  184. MDCTabBarFoundation.prototype.determineTargetFromKey = function (origin, key) {
  185. var isRTL = this.isRTL();
  186. var maxIndex = this.adapter.getTabListLength() - 1;
  187. var shouldGoToEnd = key === strings.END_KEY;
  188. var shouldDecrement = key === strings.ARROW_LEFT_KEY && !isRTL ||
  189. key === strings.ARROW_RIGHT_KEY && isRTL;
  190. var shouldIncrement = key === strings.ARROW_RIGHT_KEY && !isRTL ||
  191. key === strings.ARROW_LEFT_KEY && isRTL;
  192. var index = origin;
  193. if (shouldGoToEnd) {
  194. index = maxIndex;
  195. }
  196. else if (shouldDecrement) {
  197. index -= 1;
  198. }
  199. else if (shouldIncrement) {
  200. index += 1;
  201. }
  202. else {
  203. index = 0;
  204. }
  205. if (index < 0) {
  206. index = maxIndex;
  207. }
  208. else if (index > maxIndex) {
  209. index = 0;
  210. }
  211. return index;
  212. };
  213. /**
  214. * Calculates the scroll increment that will make the tab at the given index
  215. * visible
  216. * @param index The index of the tab
  217. * @param nextIndex The index of the next tab
  218. * @param scrollPosition The current scroll position
  219. * @param barWidth The width of the Tab Bar
  220. */
  221. MDCTabBarFoundation.prototype.calculateScrollIncrement = function (index, nextIndex, scrollPosition, barWidth) {
  222. var nextTabDimensions = this.adapter.getTabDimensionsAtIndex(nextIndex);
  223. var relativeContentLeft = nextTabDimensions.contentLeft - scrollPosition - barWidth;
  224. var relativeContentRight = nextTabDimensions.contentRight - scrollPosition;
  225. var leftIncrement = relativeContentRight - numbers.EXTRA_SCROLL_AMOUNT;
  226. var rightIncrement = relativeContentLeft + numbers.EXTRA_SCROLL_AMOUNT;
  227. if (nextIndex < index) {
  228. return Math.min(leftIncrement, 0);
  229. }
  230. return Math.max(rightIncrement, 0);
  231. };
  232. /**
  233. * Calculates the scroll increment that will make the tab at the given index
  234. * visible in RTL
  235. * @param index The index of the tab
  236. * @param nextIndex The index of the next tab
  237. * @param scrollPosition The current scroll position
  238. * @param barWidth The width of the Tab Bar
  239. * @param scrollContentWidth The width of the scroll content
  240. */
  241. MDCTabBarFoundation.prototype.calculateScrollIncrementRTL = function (index, nextIndex, scrollPosition, barWidth, scrollContentWidth) {
  242. var nextTabDimensions = this.adapter.getTabDimensionsAtIndex(nextIndex);
  243. var relativeContentLeft = scrollContentWidth - nextTabDimensions.contentLeft - scrollPosition;
  244. var relativeContentRight = scrollContentWidth -
  245. nextTabDimensions.contentRight - scrollPosition - barWidth;
  246. var leftIncrement = relativeContentRight + numbers.EXTRA_SCROLL_AMOUNT;
  247. var rightIncrement = relativeContentLeft - numbers.EXTRA_SCROLL_AMOUNT;
  248. if (nextIndex > index) {
  249. return Math.max(leftIncrement, 0);
  250. }
  251. return Math.min(rightIncrement, 0);
  252. };
  253. /**
  254. * Determines the index of the adjacent tab closest to either edge of the Tab
  255. * Bar
  256. * @param index The index of the tab
  257. * @param tabDimensions The dimensions of the tab
  258. * @param scrollPosition The current scroll position
  259. * @param barWidth The width of the tab bar
  260. */
  261. MDCTabBarFoundation.prototype.findAdjacentTabIndexClosestToEdge = function (index, tabDimensions, scrollPosition, barWidth) {
  262. /**
  263. * Tabs are laid out in the Tab Scroller like this:
  264. *
  265. * Scroll Position
  266. * +---+
  267. * | | Bar Width
  268. * | +-----------------------------------+
  269. * | | |
  270. * | V V
  271. * | +-----------------------------------+
  272. * V | Tab Scroller |
  273. * +------------+--------------+-------------------+
  274. * | Tab | Tab | Tab |
  275. * +------------+--------------+-------------------+
  276. * | |
  277. * +-----------------------------------+
  278. *
  279. * To determine the next adjacent index, we look at the Tab root left and
  280. * Tab root right, both relative to the scroll position. If the Tab root
  281. * left is less than 0, then we know it's out of view to the left. If the
  282. * Tab root right minus the bar width is greater than 0, we know the Tab is
  283. * out of view to the right. From there, we either increment or decrement
  284. * the index.
  285. */
  286. var relativeRootLeft = tabDimensions.rootLeft - scrollPosition;
  287. var relativeRootRight = tabDimensions.rootRight - scrollPosition - barWidth;
  288. var relativeRootDelta = relativeRootLeft + relativeRootRight;
  289. var leftEdgeIsCloser = relativeRootLeft < 0 || relativeRootDelta < 0;
  290. var rightEdgeIsCloser = relativeRootRight > 0 || relativeRootDelta > 0;
  291. if (leftEdgeIsCloser) {
  292. return index - 1;
  293. }
  294. if (rightEdgeIsCloser) {
  295. return index + 1;
  296. }
  297. return -1;
  298. };
  299. /**
  300. * Determines the index of the adjacent tab closest to either edge of the Tab
  301. * Bar in RTL
  302. * @param index The index of the tab
  303. * @param tabDimensions The dimensions of the tab
  304. * @param scrollPosition The current scroll position
  305. * @param barWidth The width of the tab bar
  306. * @param scrollContentWidth The width of the scroller content
  307. */
  308. MDCTabBarFoundation.prototype.findAdjacentTabIndexClosestToEdgeRTL = function (index, tabDimensions, scrollPosition, barWidth, scrollContentWidth) {
  309. var rootLeft = scrollContentWidth - tabDimensions.rootLeft - barWidth - scrollPosition;
  310. var rootRight = scrollContentWidth - tabDimensions.rootRight - scrollPosition;
  311. var rootDelta = rootLeft + rootRight;
  312. var leftEdgeIsCloser = rootLeft > 0 || rootDelta > 0;
  313. var rightEdgeIsCloser = rootRight < 0 || rootDelta < 0;
  314. if (leftEdgeIsCloser) {
  315. return index + 1;
  316. }
  317. if (rightEdgeIsCloser) {
  318. return index - 1;
  319. }
  320. return -1;
  321. };
  322. /**
  323. * Returns the key associated with a keydown event
  324. * @param evt The keydown event
  325. */
  326. MDCTabBarFoundation.prototype.getKeyFromEvent = function (evt) {
  327. if (ACCEPTABLE_KEYS.has(evt.key)) {
  328. return evt.key;
  329. }
  330. return KEYCODE_MAP.get(evt.keyCode);
  331. };
  332. MDCTabBarFoundation.prototype.isActivationKey = function (key) {
  333. return key === strings.SPACE_KEY || key === strings.ENTER_KEY;
  334. };
  335. /**
  336. * Returns whether a given index is inclusively between the ends
  337. * @param index The index to test
  338. */
  339. MDCTabBarFoundation.prototype.indexIsInRange = function (index) {
  340. return index >= 0 && index < this.adapter.getTabListLength();
  341. };
  342. /**
  343. * Returns the view's RTL property
  344. */
  345. MDCTabBarFoundation.prototype.isRTL = function () {
  346. return this.adapter.isRTL();
  347. };
  348. /**
  349. * Scrolls the tab at the given index into view for left-to-right user agents.
  350. * @param index The index of the tab to scroll into view
  351. */
  352. MDCTabBarFoundation.prototype.scrollIntoViewImpl = function (index) {
  353. var scrollPosition = this.adapter.getScrollPosition();
  354. var barWidth = this.adapter.getOffsetWidth();
  355. var tabDimensions = this.adapter.getTabDimensionsAtIndex(index);
  356. var nextIndex = this.findAdjacentTabIndexClosestToEdge(index, tabDimensions, scrollPosition, barWidth);
  357. if (!this.indexIsInRange(nextIndex)) {
  358. return;
  359. }
  360. var scrollIncrement = this.calculateScrollIncrement(index, nextIndex, scrollPosition, barWidth);
  361. this.adapter.incrementScroll(scrollIncrement);
  362. };
  363. /**
  364. * Scrolls the tab at the given index into view in RTL
  365. * @param index The tab index to make visible
  366. */
  367. MDCTabBarFoundation.prototype.scrollIntoViewImplRTL = function (index) {
  368. var scrollPosition = this.adapter.getScrollPosition();
  369. var barWidth = this.adapter.getOffsetWidth();
  370. var tabDimensions = this.adapter.getTabDimensionsAtIndex(index);
  371. var scrollWidth = this.adapter.getScrollContentWidth();
  372. var nextIndex = this.findAdjacentTabIndexClosestToEdgeRTL(index, tabDimensions, scrollPosition, barWidth, scrollWidth);
  373. if (!this.indexIsInRange(nextIndex)) {
  374. return;
  375. }
  376. var scrollIncrement = this.calculateScrollIncrementRTL(index, nextIndex, scrollPosition, barWidth, scrollWidth);
  377. this.adapter.incrementScroll(scrollIncrement);
  378. };
  379. return MDCTabBarFoundation;
  380. }(MDCFoundation));
  381. export { MDCTabBarFoundation };
  382. // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
  383. export default MDCTabBarFoundation;
  384. //# sourceMappingURL=foundation.js.map