foundation.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  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, __values } from "tslib";
  24. import { MDCFoundation } from '@material/base/foundation';
  25. import { Corner, CornerBit, cssClasses, numbers, strings } from './constants';
  26. /** MDC Menu Surface Foundation */
  27. var MDCMenuSurfaceFoundation = /** @class */ (function (_super) {
  28. __extends(MDCMenuSurfaceFoundation, _super);
  29. function MDCMenuSurfaceFoundation(adapter) {
  30. var _this = _super.call(this, __assign(__assign({}, MDCMenuSurfaceFoundation.defaultAdapter), adapter)) || this;
  31. _this.isSurfaceOpen = false;
  32. _this.isQuickOpen = false;
  33. _this.isHoistedElement = false;
  34. _this.isFixedPosition = false;
  35. _this.isHorizontallyCenteredOnViewport = false;
  36. _this.maxHeight = 0;
  37. _this.openBottomBias = 0;
  38. _this.openAnimationEndTimerId = 0;
  39. _this.closeAnimationEndTimerId = 0;
  40. _this.animationRequestId = 0;
  41. _this.anchorCorner = Corner.TOP_START;
  42. /**
  43. * Corner of the menu surface to which menu surface is attached to anchor.
  44. *
  45. * Anchor corner --->+----------+
  46. * | ANCHOR |
  47. * +----------+
  48. * Origin corner --->+--------------+
  49. * | |
  50. * | |
  51. * | MENU SURFACE |
  52. * | |
  53. * | |
  54. * +--------------+
  55. */
  56. _this.originCorner = Corner.TOP_START;
  57. _this.anchorMargin = { top: 0, right: 0, bottom: 0, left: 0 };
  58. _this.position = { x: 0, y: 0 };
  59. return _this;
  60. }
  61. Object.defineProperty(MDCMenuSurfaceFoundation, "cssClasses", {
  62. get: function () {
  63. return cssClasses;
  64. },
  65. enumerable: false,
  66. configurable: true
  67. });
  68. Object.defineProperty(MDCMenuSurfaceFoundation, "strings", {
  69. get: function () {
  70. return strings;
  71. },
  72. enumerable: false,
  73. configurable: true
  74. });
  75. Object.defineProperty(MDCMenuSurfaceFoundation, "numbers", {
  76. get: function () {
  77. return numbers;
  78. },
  79. enumerable: false,
  80. configurable: true
  81. });
  82. Object.defineProperty(MDCMenuSurfaceFoundation, "Corner", {
  83. get: function () {
  84. return Corner;
  85. },
  86. enumerable: false,
  87. configurable: true
  88. });
  89. Object.defineProperty(MDCMenuSurfaceFoundation, "defaultAdapter", {
  90. /**
  91. * @see {@link MDCMenuSurfaceAdapter} for typing information on parameters and return types.
  92. */
  93. get: function () {
  94. // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
  95. return {
  96. addClass: function () { return undefined; },
  97. removeClass: function () { return undefined; },
  98. hasClass: function () { return false; },
  99. hasAnchor: function () { return false; },
  100. isElementInContainer: function () { return false; },
  101. isFocused: function () { return false; },
  102. isRtl: function () { return false; },
  103. getInnerDimensions: function () { return ({ height: 0, width: 0 }); },
  104. getAnchorDimensions: function () { return null; },
  105. getViewportDimensions: function () { return ({ height: 0, width: 0 }); },
  106. getBodyDimensions: function () { return ({ height: 0, width: 0 }); },
  107. getWindowScroll: function () { return ({ x: 0, y: 0 }); },
  108. setPosition: function () { return undefined; },
  109. setMaxHeight: function () { return undefined; },
  110. setTransformOrigin: function () { return undefined; },
  111. saveFocus: function () { return undefined; },
  112. restoreFocus: function () { return undefined; },
  113. notifyClose: function () { return undefined; },
  114. notifyClosing: function () { return undefined; },
  115. notifyOpen: function () { return undefined; },
  116. notifyOpening: function () { return undefined; },
  117. registerWindowEventHandler: function () { return undefined; },
  118. deregisterWindowEventHandler: function () { return undefined; },
  119. };
  120. // tslint:enable:object-literal-sort-keys
  121. },
  122. enumerable: false,
  123. configurable: true
  124. });
  125. MDCMenuSurfaceFoundation.prototype.init = function () {
  126. var _a = MDCMenuSurfaceFoundation.cssClasses, ROOT = _a.ROOT, OPEN = _a.OPEN;
  127. if (!this.adapter.hasClass(ROOT)) {
  128. throw new Error(ROOT + " class required in root element.");
  129. }
  130. if (this.adapter.hasClass(OPEN)) {
  131. this.isSurfaceOpen = true;
  132. }
  133. this.resizeListener = this.handleResize.bind(this);
  134. this.adapter.registerWindowEventHandler('resize', this.resizeListener);
  135. };
  136. MDCMenuSurfaceFoundation.prototype.destroy = function () {
  137. clearTimeout(this.openAnimationEndTimerId);
  138. clearTimeout(this.closeAnimationEndTimerId);
  139. // Cancel any currently running animations.
  140. cancelAnimationFrame(this.animationRequestId);
  141. this.adapter.deregisterWindowEventHandler('resize', this.resizeListener);
  142. };
  143. /**
  144. * @param corner Default anchor corner alignment of top-left menu surface
  145. * corner.
  146. */
  147. MDCMenuSurfaceFoundation.prototype.setAnchorCorner = function (corner) {
  148. this.anchorCorner = corner;
  149. };
  150. /**
  151. * Flip menu corner horizontally.
  152. */
  153. MDCMenuSurfaceFoundation.prototype.flipCornerHorizontally = function () {
  154. this.originCorner = this.originCorner ^ CornerBit.RIGHT;
  155. };
  156. /**
  157. * @param margin Set of margin values from anchor.
  158. */
  159. MDCMenuSurfaceFoundation.prototype.setAnchorMargin = function (margin) {
  160. this.anchorMargin.top = margin.top || 0;
  161. this.anchorMargin.right = margin.right || 0;
  162. this.anchorMargin.bottom = margin.bottom || 0;
  163. this.anchorMargin.left = margin.left || 0;
  164. };
  165. /** Used to indicate if the menu-surface is hoisted to the body. */
  166. MDCMenuSurfaceFoundation.prototype.setIsHoisted = function (isHoisted) {
  167. this.isHoistedElement = isHoisted;
  168. };
  169. /**
  170. * Used to set the menu-surface calculations based on a fixed position menu.
  171. */
  172. MDCMenuSurfaceFoundation.prototype.setFixedPosition = function (isFixedPosition) {
  173. this.isFixedPosition = isFixedPosition;
  174. };
  175. /**
  176. * @return Returns true if menu is in fixed (`position: fixed`) position.
  177. */
  178. MDCMenuSurfaceFoundation.prototype.isFixed = function () {
  179. return this.isFixedPosition;
  180. };
  181. /** Sets the menu-surface position on the page. */
  182. MDCMenuSurfaceFoundation.prototype.setAbsolutePosition = function (x, y) {
  183. this.position.x = this.isFinite(x) ? x : 0;
  184. this.position.y = this.isFinite(y) ? y : 0;
  185. };
  186. /** Sets whether menu-surface should be horizontally centered to viewport. */
  187. MDCMenuSurfaceFoundation.prototype.setIsHorizontallyCenteredOnViewport = function (isCentered) {
  188. this.isHorizontallyCenteredOnViewport = isCentered;
  189. };
  190. MDCMenuSurfaceFoundation.prototype.setQuickOpen = function (quickOpen) {
  191. this.isQuickOpen = quickOpen;
  192. };
  193. /**
  194. * Sets maximum menu-surface height on open.
  195. * @param maxHeight The desired max-height. Set to 0 (default) to
  196. * automatically calculate max height based on available viewport space.
  197. */
  198. MDCMenuSurfaceFoundation.prototype.setMaxHeight = function (maxHeight) {
  199. this.maxHeight = maxHeight;
  200. };
  201. /**
  202. * Set to a positive integer to influence the menu to preferentially open
  203. * below the anchor instead of above.
  204. * @param bias A value of `x` simulates an extra `x` pixels of available space
  205. * below the menu during positioning calculations.
  206. */
  207. MDCMenuSurfaceFoundation.prototype.setOpenBottomBias = function (bias) {
  208. this.openBottomBias = bias;
  209. };
  210. MDCMenuSurfaceFoundation.prototype.isOpen = function () {
  211. return this.isSurfaceOpen;
  212. };
  213. /**
  214. * Open the menu surface.
  215. */
  216. MDCMenuSurfaceFoundation.prototype.open = function () {
  217. var _this = this;
  218. if (this.isSurfaceOpen) {
  219. return;
  220. }
  221. this.adapter.notifyOpening();
  222. this.adapter.saveFocus();
  223. if (this.isQuickOpen) {
  224. this.isSurfaceOpen = true;
  225. this.adapter.addClass(MDCMenuSurfaceFoundation.cssClasses.OPEN);
  226. this.dimensions = this.adapter.getInnerDimensions();
  227. this.autoposition();
  228. this.adapter.notifyOpen();
  229. }
  230. else {
  231. this.adapter.addClass(MDCMenuSurfaceFoundation.cssClasses.ANIMATING_OPEN);
  232. this.animationRequestId = requestAnimationFrame(function () {
  233. _this.dimensions = _this.adapter.getInnerDimensions();
  234. _this.autoposition();
  235. _this.adapter.addClass(MDCMenuSurfaceFoundation.cssClasses.OPEN);
  236. _this.openAnimationEndTimerId = setTimeout(function () {
  237. _this.openAnimationEndTimerId = 0;
  238. _this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.ANIMATING_OPEN);
  239. _this.adapter.notifyOpen();
  240. }, numbers.TRANSITION_OPEN_DURATION);
  241. });
  242. this.isSurfaceOpen = true;
  243. }
  244. this.adapter.registerWindowEventHandler('resize', this.resizeListener);
  245. };
  246. /**
  247. * Closes the menu surface.
  248. */
  249. MDCMenuSurfaceFoundation.prototype.close = function (skipRestoreFocus) {
  250. var _this = this;
  251. if (skipRestoreFocus === void 0) { skipRestoreFocus = false; }
  252. if (!this.isSurfaceOpen) {
  253. return;
  254. }
  255. this.adapter.notifyClosing();
  256. this.adapter.deregisterWindowEventHandler('resize', this.resizeListener);
  257. if (this.isQuickOpen) {
  258. this.isSurfaceOpen = false;
  259. if (!skipRestoreFocus) {
  260. this.maybeRestoreFocus();
  261. }
  262. this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.OPEN);
  263. this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.IS_OPEN_BELOW);
  264. this.adapter.notifyClose();
  265. return;
  266. }
  267. this.adapter.addClass(MDCMenuSurfaceFoundation.cssClasses.ANIMATING_CLOSED);
  268. requestAnimationFrame(function () {
  269. _this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.OPEN);
  270. _this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.IS_OPEN_BELOW);
  271. _this.closeAnimationEndTimerId = setTimeout(function () {
  272. _this.closeAnimationEndTimerId = 0;
  273. _this.adapter.removeClass(MDCMenuSurfaceFoundation.cssClasses.ANIMATING_CLOSED);
  274. _this.adapter.notifyClose();
  275. }, numbers.TRANSITION_CLOSE_DURATION);
  276. });
  277. this.isSurfaceOpen = false;
  278. if (!skipRestoreFocus) {
  279. this.maybeRestoreFocus();
  280. }
  281. };
  282. /** Handle clicks and close if not within menu-surface element. */
  283. MDCMenuSurfaceFoundation.prototype.handleBodyClick = function (evt) {
  284. var el = evt.target;
  285. if (this.adapter.isElementInContainer(el)) {
  286. return;
  287. }
  288. this.close();
  289. };
  290. /** Handle keys that close the surface. */
  291. MDCMenuSurfaceFoundation.prototype.handleKeydown = function (evt) {
  292. var keyCode = evt.keyCode, key = evt.key;
  293. var isEscape = key === 'Escape' || keyCode === 27;
  294. if (isEscape) {
  295. this.close();
  296. }
  297. };
  298. /** Handles resize events on the window. */
  299. MDCMenuSurfaceFoundation.prototype.handleResize = function () {
  300. this.dimensions = this.adapter.getInnerDimensions();
  301. this.autoposition();
  302. };
  303. MDCMenuSurfaceFoundation.prototype.autoposition = function () {
  304. var _a;
  305. // Compute measurements for autoposition methods reuse.
  306. this.measurements = this.getAutoLayoutmeasurements();
  307. var corner = this.getoriginCorner();
  308. var maxMenuSurfaceHeight = this.getMenuSurfaceMaxHeight(corner);
  309. var verticalAlignment = this.hasBit(corner, CornerBit.BOTTOM) ? 'bottom' : 'top';
  310. var horizontalAlignment = this.hasBit(corner, CornerBit.RIGHT) ? 'right' : 'left';
  311. var horizontalOffset = this.getHorizontalOriginOffset(corner);
  312. var verticalOffset = this.getVerticalOriginOffset(corner);
  313. var _b = this.measurements, anchorSize = _b.anchorSize, surfaceSize = _b.surfaceSize;
  314. var position = (_a = {},
  315. _a[horizontalAlignment] = horizontalOffset,
  316. _a[verticalAlignment] = verticalOffset,
  317. _a);
  318. // Center align when anchor width is comparable or greater than menu
  319. // surface, otherwise keep corner.
  320. if (anchorSize.width / surfaceSize.width >
  321. numbers.ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO) {
  322. horizontalAlignment = 'center';
  323. }
  324. // If the menu-surface has been hoisted to the body, it's no longer relative
  325. // to the anchor element
  326. if (this.isHoistedElement || this.isFixedPosition) {
  327. this.adjustPositionForHoistedElement(position);
  328. }
  329. this.adapter.setTransformOrigin(horizontalAlignment + " " + verticalAlignment);
  330. this.adapter.setPosition(position);
  331. this.adapter.setMaxHeight(maxMenuSurfaceHeight ? maxMenuSurfaceHeight + 'px' : '');
  332. // If it is opened from the top then add is-open-below class
  333. if (!this.hasBit(corner, CornerBit.BOTTOM)) {
  334. this.adapter.addClass(MDCMenuSurfaceFoundation.cssClasses.IS_OPEN_BELOW);
  335. }
  336. };
  337. /**
  338. * @return Measurements used to position menu surface popup.
  339. */
  340. MDCMenuSurfaceFoundation.prototype.getAutoLayoutmeasurements = function () {
  341. var anchorRect = this.adapter.getAnchorDimensions();
  342. var bodySize = this.adapter.getBodyDimensions();
  343. var viewportSize = this.adapter.getViewportDimensions();
  344. var windowScroll = this.adapter.getWindowScroll();
  345. if (!anchorRect) {
  346. // tslint:disable:object-literal-sort-keys Positional properties are more readable when they're grouped together
  347. anchorRect = {
  348. top: this.position.y,
  349. right: this.position.x,
  350. bottom: this.position.y,
  351. left: this.position.x,
  352. width: 0,
  353. height: 0,
  354. };
  355. // tslint:enable:object-literal-sort-keys
  356. }
  357. return {
  358. anchorSize: anchorRect,
  359. bodySize: bodySize,
  360. surfaceSize: this.dimensions,
  361. viewportDistance: {
  362. // tslint:disable:object-literal-sort-keys Positional properties are more readable when they're grouped together
  363. top: anchorRect.top,
  364. right: viewportSize.width - anchorRect.right,
  365. bottom: viewportSize.height - anchorRect.bottom,
  366. left: anchorRect.left,
  367. // tslint:enable:object-literal-sort-keys
  368. },
  369. viewportSize: viewportSize,
  370. windowScroll: windowScroll,
  371. };
  372. };
  373. /**
  374. * Computes the corner of the anchor from which to animate and position the
  375. * menu surface.
  376. *
  377. * Only LEFT or RIGHT bit is used to position the menu surface ignoring RTL
  378. * context. E.g., menu surface will be positioned from right side on TOP_END.
  379. */
  380. MDCMenuSurfaceFoundation.prototype.getoriginCorner = function () {
  381. var corner = this.originCorner;
  382. var _a = this.measurements, viewportDistance = _a.viewportDistance, anchorSize = _a.anchorSize, surfaceSize = _a.surfaceSize;
  383. var MARGIN_TO_EDGE = MDCMenuSurfaceFoundation.numbers.MARGIN_TO_EDGE;
  384. var isAnchoredToBottom = this.hasBit(this.anchorCorner, CornerBit.BOTTOM);
  385. var availableTop;
  386. var availableBottom;
  387. if (isAnchoredToBottom) {
  388. availableTop =
  389. viewportDistance.top - MARGIN_TO_EDGE + this.anchorMargin.bottom;
  390. availableBottom =
  391. viewportDistance.bottom - MARGIN_TO_EDGE - this.anchorMargin.bottom;
  392. }
  393. else {
  394. availableTop =
  395. viewportDistance.top - MARGIN_TO_EDGE + this.anchorMargin.top;
  396. availableBottom = viewportDistance.bottom - MARGIN_TO_EDGE +
  397. anchorSize.height - this.anchorMargin.top;
  398. }
  399. var isAvailableBottom = availableBottom - surfaceSize.height > 0;
  400. if (!isAvailableBottom &&
  401. availableTop > availableBottom + this.openBottomBias) {
  402. // Attach bottom side of surface to the anchor.
  403. corner = this.setBit(corner, CornerBit.BOTTOM);
  404. }
  405. var isRtl = this.adapter.isRtl();
  406. var isFlipRtl = this.hasBit(this.anchorCorner, CornerBit.FLIP_RTL);
  407. var hasRightBit = this.hasBit(this.anchorCorner, CornerBit.RIGHT) ||
  408. this.hasBit(corner, CornerBit.RIGHT);
  409. // Whether surface attached to right side of anchor element.
  410. var isAnchoredToRight = false;
  411. // Anchored to start
  412. if (isRtl && isFlipRtl) {
  413. isAnchoredToRight = !hasRightBit;
  414. }
  415. else {
  416. // Anchored to right
  417. isAnchoredToRight = hasRightBit;
  418. }
  419. var availableLeft;
  420. var availableRight;
  421. if (isAnchoredToRight) {
  422. availableLeft =
  423. viewportDistance.left + anchorSize.width + this.anchorMargin.left;
  424. availableRight = viewportDistance.right - this.anchorMargin.left;
  425. }
  426. else {
  427. availableLeft = viewportDistance.left + this.anchorMargin.left;
  428. availableRight =
  429. viewportDistance.right + anchorSize.width - this.anchorMargin.left;
  430. }
  431. var isAvailableLeft = availableLeft - surfaceSize.width > 0;
  432. var isAvailableRight = availableRight - surfaceSize.width > 0;
  433. var isOriginCornerAlignedToEnd = this.hasBit(corner, CornerBit.FLIP_RTL) &&
  434. this.hasBit(corner, CornerBit.RIGHT);
  435. if (isAvailableRight && isOriginCornerAlignedToEnd && isRtl ||
  436. !isAvailableLeft && isOriginCornerAlignedToEnd) {
  437. // Attach left side of surface to the anchor.
  438. corner = this.unsetBit(corner, CornerBit.RIGHT);
  439. }
  440. else if (isAvailableLeft && isAnchoredToRight && isRtl ||
  441. (isAvailableLeft && !isAnchoredToRight && hasRightBit) ||
  442. (!isAvailableRight && availableLeft >= availableRight)) {
  443. // Attach right side of surface to the anchor.
  444. corner = this.setBit(corner, CornerBit.RIGHT);
  445. }
  446. return corner;
  447. };
  448. /**
  449. * @param corner Origin corner of the menu surface.
  450. * @return Maximum height of the menu surface, based on available space. 0
  451. * indicates should not be set.
  452. */
  453. MDCMenuSurfaceFoundation.prototype.getMenuSurfaceMaxHeight = function (corner) {
  454. if (this.maxHeight > 0) {
  455. return this.maxHeight;
  456. }
  457. var viewportDistance = this.measurements.viewportDistance;
  458. var maxHeight = 0;
  459. var isBottomAligned = this.hasBit(corner, CornerBit.BOTTOM);
  460. var isBottomAnchored = this.hasBit(this.anchorCorner, CornerBit.BOTTOM);
  461. var MARGIN_TO_EDGE = MDCMenuSurfaceFoundation.numbers.MARGIN_TO_EDGE;
  462. // When maximum height is not specified, it is handled from CSS.
  463. if (isBottomAligned) {
  464. maxHeight = viewportDistance.top + this.anchorMargin.top - MARGIN_TO_EDGE;
  465. if (!isBottomAnchored) {
  466. maxHeight += this.measurements.anchorSize.height;
  467. }
  468. }
  469. else {
  470. maxHeight = viewportDistance.bottom - this.anchorMargin.bottom +
  471. this.measurements.anchorSize.height - MARGIN_TO_EDGE;
  472. if (isBottomAnchored) {
  473. maxHeight -= this.measurements.anchorSize.height;
  474. }
  475. }
  476. return maxHeight;
  477. };
  478. /**
  479. * @param corner Origin corner of the menu surface.
  480. * @return Horizontal offset of menu surface origin corner from corresponding
  481. * anchor corner.
  482. */
  483. MDCMenuSurfaceFoundation.prototype.getHorizontalOriginOffset = function (corner) {
  484. var anchorSize = this.measurements.anchorSize;
  485. // isRightAligned corresponds to using the 'right' property on the surface.
  486. var isRightAligned = this.hasBit(corner, CornerBit.RIGHT);
  487. var avoidHorizontalOverlap = this.hasBit(this.anchorCorner, CornerBit.RIGHT);
  488. if (isRightAligned) {
  489. var rightOffset = avoidHorizontalOverlap ?
  490. anchorSize.width - this.anchorMargin.left :
  491. this.anchorMargin.right;
  492. // For hoisted or fixed elements, adjust the offset by the difference
  493. // between viewport width and body width so when we calculate the right
  494. // value (`adjustPositionForHoistedElement`) based on the element
  495. // position, the right property is correct.
  496. if (this.isHoistedElement || this.isFixedPosition) {
  497. return rightOffset -
  498. (this.measurements.viewportSize.width -
  499. this.measurements.bodySize.width);
  500. }
  501. return rightOffset;
  502. }
  503. return avoidHorizontalOverlap ? anchorSize.width - this.anchorMargin.right :
  504. this.anchorMargin.left;
  505. };
  506. /**
  507. * @param corner Origin corner of the menu surface.
  508. * @return Vertical offset of menu surface origin corner from corresponding
  509. * anchor corner.
  510. */
  511. MDCMenuSurfaceFoundation.prototype.getVerticalOriginOffset = function (corner) {
  512. var anchorSize = this.measurements.anchorSize;
  513. var isBottomAligned = this.hasBit(corner, CornerBit.BOTTOM);
  514. var avoidVerticalOverlap = this.hasBit(this.anchorCorner, CornerBit.BOTTOM);
  515. var y = 0;
  516. if (isBottomAligned) {
  517. y = avoidVerticalOverlap ? anchorSize.height - this.anchorMargin.top :
  518. -this.anchorMargin.bottom;
  519. }
  520. else {
  521. y = avoidVerticalOverlap ?
  522. (anchorSize.height + this.anchorMargin.bottom) :
  523. this.anchorMargin.top;
  524. }
  525. return y;
  526. };
  527. /**
  528. * Calculates the offsets for positioning the menu-surface when the
  529. * menu-surface has been hoisted to the body.
  530. */
  531. MDCMenuSurfaceFoundation.prototype.adjustPositionForHoistedElement = function (position) {
  532. var e_1, _a;
  533. var _b = this.measurements, windowScroll = _b.windowScroll, viewportDistance = _b.viewportDistance, surfaceSize = _b.surfaceSize, viewportSize = _b.viewportSize;
  534. var props = Object.keys(position);
  535. try {
  536. for (var props_1 = __values(props), props_1_1 = props_1.next(); !props_1_1.done; props_1_1 = props_1.next()) {
  537. var prop = props_1_1.value;
  538. var value = position[prop] || 0;
  539. if (this.isHorizontallyCenteredOnViewport &&
  540. (prop === 'left' || prop === 'right')) {
  541. position[prop] = (viewportSize.width - surfaceSize.width) / 2;
  542. continue;
  543. }
  544. // Hoisted surfaces need to have the anchor elements location on the page
  545. // added to the position properties for proper alignment on the body.
  546. value += viewportDistance[prop];
  547. // Surfaces that are absolutely positioned need to have additional
  548. // calculations for scroll and bottom positioning.
  549. if (!this.isFixedPosition) {
  550. if (prop === 'top') {
  551. value += windowScroll.y;
  552. }
  553. else if (prop === 'bottom') {
  554. value -= windowScroll.y;
  555. }
  556. else if (prop === 'left') {
  557. value += windowScroll.x;
  558. }
  559. else { // prop === 'right'
  560. value -= windowScroll.x;
  561. }
  562. }
  563. position[prop] = value;
  564. }
  565. }
  566. catch (e_1_1) { e_1 = { error: e_1_1 }; }
  567. finally {
  568. try {
  569. if (props_1_1 && !props_1_1.done && (_a = props_1.return)) _a.call(props_1);
  570. }
  571. finally { if (e_1) throw e_1.error; }
  572. }
  573. };
  574. /**
  575. * The last focused element when the menu surface was opened should regain
  576. * focus, if the user is focused on or within the menu surface when it is
  577. * closed.
  578. */
  579. MDCMenuSurfaceFoundation.prototype.maybeRestoreFocus = function () {
  580. var _this = this;
  581. var isRootFocused = this.adapter.isFocused();
  582. var ownerDocument = this.adapter.getOwnerDocument ?
  583. this.adapter.getOwnerDocument() :
  584. document;
  585. var childHasFocus = ownerDocument.activeElement &&
  586. this.adapter.isElementInContainer(ownerDocument.activeElement);
  587. if (isRootFocused || childHasFocus) {
  588. // Wait before restoring focus when closing the menu surface. This is
  589. // important because if a touch event triggered the menu close, and the
  590. // subsequent mouse event occurs after focus is restored, then the
  591. // restored focus would be lost.
  592. setTimeout(function () {
  593. _this.adapter.restoreFocus();
  594. }, numbers.TOUCH_EVENT_WAIT_MS);
  595. }
  596. };
  597. MDCMenuSurfaceFoundation.prototype.hasBit = function (corner, bit) {
  598. return Boolean(corner & bit); // tslint:disable-line:no-bitwise
  599. };
  600. MDCMenuSurfaceFoundation.prototype.setBit = function (corner, bit) {
  601. return corner | bit; // tslint:disable-line:no-bitwise
  602. };
  603. MDCMenuSurfaceFoundation.prototype.unsetBit = function (corner, bit) {
  604. return corner ^ bit;
  605. };
  606. /**
  607. * isFinite that doesn't force conversion to number type.
  608. * Equivalent to Number.isFinite in ES2015, which is not supported in IE.
  609. */
  610. MDCMenuSurfaceFoundation.prototype.isFinite = function (num) {
  611. return typeof num === 'number' && isFinite(num);
  612. };
  613. return MDCMenuSurfaceFoundation;
  614. }(MDCFoundation));
  615. export { MDCMenuSurfaceFoundation };
  616. // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
  617. export default MDCMenuSurfaceFoundation;
  618. //# sourceMappingURL=foundation.js.map