index-a96d31ae.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. 'use strict';
  5. const index$1 = require('./index-c8d52405.js');
  6. const hardwareBackButton = require('./hardware-back-button-3d2b1004.js');
  7. const index = require('./index-cc858e97.js');
  8. const helpers = require('./helpers-8a48fdea.js');
  9. const ionicGlobal = require('./ionic-global-6dea5a96.js');
  10. const animation = require('./animation-ab2d3449.js');
  11. /**
  12. * baseAnimation
  13. * Base class which is extended by the various types. Each
  14. * type will provide their own animations for open and close
  15. * and registers itself with Menu.
  16. */
  17. const baseAnimation = (isIos) => {
  18. // https://material.io/guidelines/motion/movement.html#movement-movement-in-out-of-screen-bounds
  19. // https://material.io/guidelines/motion/duration-easing.html#duration-easing-natural-easing-curves
  20. /**
  21. * "Apply the sharp curve to items temporarily leaving the screen that may return
  22. * from the same exit point. When they return, use the deceleration curve. On mobile,
  23. * this transition typically occurs over 300ms" -- MD Motion Guide
  24. */
  25. return animation.createAnimation().duration(isIos ? 400 : 300);
  26. };
  27. /**
  28. * Menu Overlay Type
  29. * The menu slides over the content. The content
  30. * itself, which is under the menu, does not move.
  31. */
  32. const menuOverlayAnimation = (menu) => {
  33. let closedX;
  34. let openedX;
  35. const width = menu.width + 8;
  36. const menuAnimation = animation.createAnimation();
  37. const backdropAnimation = animation.createAnimation();
  38. if (menu.isEndSide) {
  39. // right side
  40. closedX = width + 'px';
  41. openedX = '0px';
  42. }
  43. else {
  44. // left side
  45. closedX = -width + 'px';
  46. openedX = '0px';
  47. }
  48. menuAnimation.addElement(menu.menuInnerEl).fromTo('transform', `translateX(${closedX})`, `translateX(${openedX})`);
  49. const mode = ionicGlobal.getIonMode(menu);
  50. const isIos = mode === 'ios';
  51. const opacity = isIos ? 0.2 : 0.25;
  52. backdropAnimation.addElement(menu.backdropEl).fromTo('opacity', 0.01, opacity);
  53. return baseAnimation(isIos).addAnimation([menuAnimation, backdropAnimation]);
  54. };
  55. /**
  56. * Menu Push Type
  57. * The content slides over to reveal the menu underneath.
  58. * The menu itself also slides over to reveal its bad self.
  59. */
  60. const menuPushAnimation = (menu) => {
  61. let contentOpenedX;
  62. let menuClosedX;
  63. const mode = ionicGlobal.getIonMode(menu);
  64. const width = menu.width;
  65. if (menu.isEndSide) {
  66. contentOpenedX = -width + 'px';
  67. menuClosedX = width + 'px';
  68. }
  69. else {
  70. contentOpenedX = width + 'px';
  71. menuClosedX = -width + 'px';
  72. }
  73. const menuAnimation = animation.createAnimation()
  74. .addElement(menu.menuInnerEl)
  75. .fromTo('transform', `translateX(${menuClosedX})`, 'translateX(0px)');
  76. const contentAnimation = animation.createAnimation()
  77. .addElement(menu.contentEl)
  78. .fromTo('transform', 'translateX(0px)', `translateX(${contentOpenedX})`);
  79. const backdropAnimation = animation.createAnimation().addElement(menu.backdropEl).fromTo('opacity', 0.01, 0.32);
  80. return baseAnimation(mode === 'ios').addAnimation([menuAnimation, contentAnimation, backdropAnimation]);
  81. };
  82. /**
  83. * Menu Reveal Type
  84. * The content slides over to reveal the menu underneath.
  85. * The menu itself, which is under the content, does not move.
  86. */
  87. const menuRevealAnimation = (menu) => {
  88. const mode = ionicGlobal.getIonMode(menu);
  89. const openedX = menu.width * (menu.isEndSide ? -1 : 1) + 'px';
  90. const contentOpen = animation.createAnimation()
  91. .addElement(menu.contentEl) // REVIEW
  92. .fromTo('transform', 'translateX(0px)', `translateX(${openedX})`);
  93. return baseAnimation(mode === 'ios').addAnimation(contentOpen);
  94. };
  95. const createMenuController = () => {
  96. const menuAnimations = new Map();
  97. const menus = [];
  98. const open = async (menu) => {
  99. const menuEl = await get(menu, true);
  100. if (menuEl) {
  101. return menuEl.open();
  102. }
  103. return false;
  104. };
  105. const close = async (menu) => {
  106. const menuEl = await (menu !== undefined ? get(menu, true) : getOpen());
  107. if (menuEl !== undefined) {
  108. return menuEl.close();
  109. }
  110. return false;
  111. };
  112. const toggle = async (menu) => {
  113. const menuEl = await get(menu, true);
  114. if (menuEl) {
  115. return menuEl.toggle();
  116. }
  117. return false;
  118. };
  119. const enable = async (shouldEnable, menu) => {
  120. const menuEl = await get(menu);
  121. if (menuEl) {
  122. menuEl.disabled = !shouldEnable;
  123. }
  124. return menuEl;
  125. };
  126. const swipeGesture = async (shouldEnable, menu) => {
  127. const menuEl = await get(menu);
  128. if (menuEl) {
  129. menuEl.swipeGesture = shouldEnable;
  130. }
  131. return menuEl;
  132. };
  133. const isOpen = async (menu) => {
  134. if (menu != null) {
  135. const menuEl = await get(menu);
  136. // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
  137. return menuEl !== undefined && menuEl.isOpen();
  138. }
  139. else {
  140. const menuEl = await getOpen();
  141. return menuEl !== undefined;
  142. }
  143. };
  144. const isEnabled = async (menu) => {
  145. const menuEl = await get(menu);
  146. if (menuEl) {
  147. return !menuEl.disabled;
  148. }
  149. return false;
  150. };
  151. /**
  152. * Finds and returns the menu specified by "menu" if registered.
  153. * @param menu - The side or ID of the desired menu
  154. * @param logOnMultipleSideMenus - If true, this function will log a warning
  155. * if "menu" is a side but multiple menus on the same side were found. Since this function
  156. * is used in multiple places, we default this log to false so that the calling
  157. * functions can choose whether or not it is appropriate to log this warning.
  158. */
  159. const get = async (menu, logOnMultipleSideMenus = false) => {
  160. await waitUntilReady();
  161. if (menu === 'start' || menu === 'end') {
  162. // there could be more than one menu on the same side
  163. // so first try to get the enabled one
  164. const menuRefs = menus.filter((m) => m.side === menu && !m.disabled);
  165. if (menuRefs.length >= 1) {
  166. if (menuRefs.length > 1 && logOnMultipleSideMenus) {
  167. index.printIonWarning(`menuController queried for a menu on the "${menu}" side, but ${menuRefs.length} menus were found. The first menu reference will be used. If this is not the behavior you want then pass the ID of the menu instead of its side.`, menuRefs.map((m) => m.el));
  168. }
  169. return menuRefs[0].el;
  170. }
  171. // didn't find a menu side that is enabled
  172. // so try to get the first menu side found
  173. const sideMenuRefs = menus.filter((m) => m.side === menu);
  174. if (sideMenuRefs.length >= 1) {
  175. if (sideMenuRefs.length > 1 && logOnMultipleSideMenus) {
  176. index.printIonWarning(`menuController queried for a menu on the "${menu}" side, but ${sideMenuRefs.length} menus were found. The first menu reference will be used. If this is not the behavior you want then pass the ID of the menu instead of its side.`, sideMenuRefs.map((m) => m.el));
  177. }
  178. return sideMenuRefs[0].el;
  179. }
  180. }
  181. else if (menu != null) {
  182. // the menuId was not left or right
  183. // so try to get the menu by its "id"
  184. return find((m) => m.menuId === menu);
  185. }
  186. // return the first enabled menu
  187. const menuEl = find((m) => !m.disabled);
  188. if (menuEl) {
  189. return menuEl;
  190. }
  191. // get the first menu in the array, if one exists
  192. return menus.length > 0 ? menus[0].el : undefined;
  193. };
  194. /**
  195. * Get the instance of the opened menu. Returns `null` if a menu is not found.
  196. */
  197. const getOpen = async () => {
  198. await waitUntilReady();
  199. return _getOpenSync();
  200. };
  201. /**
  202. * Get all menu instances.
  203. */
  204. const getMenus = async () => {
  205. await waitUntilReady();
  206. return getMenusSync();
  207. };
  208. /**
  209. * Get whether or not a menu is animating. Returns `true` if any
  210. * menu is currently animating.
  211. */
  212. const isAnimating = async () => {
  213. await waitUntilReady();
  214. return isAnimatingSync();
  215. };
  216. const registerAnimation = (name, animation) => {
  217. menuAnimations.set(name, animation);
  218. };
  219. const _register = (menu) => {
  220. if (menus.indexOf(menu) < 0) {
  221. menus.push(menu);
  222. }
  223. };
  224. const _unregister = (menu) => {
  225. const index = menus.indexOf(menu);
  226. if (index > -1) {
  227. menus.splice(index, 1);
  228. }
  229. };
  230. const _setOpen = async (menu, shouldOpen, animated, role) => {
  231. if (isAnimatingSync()) {
  232. return false;
  233. }
  234. if (shouldOpen) {
  235. const openedMenu = await getOpen();
  236. if (openedMenu && menu.el !== openedMenu) {
  237. await openedMenu.setOpen(false, false);
  238. }
  239. }
  240. return menu._setOpen(shouldOpen, animated, role);
  241. };
  242. const _createAnimation = (type, menuCmp) => {
  243. const animationBuilder = menuAnimations.get(type); // TODO(FW-2832): type
  244. if (!animationBuilder) {
  245. throw new Error('animation not registered');
  246. }
  247. const animation = animationBuilder(menuCmp);
  248. return animation;
  249. };
  250. const _getOpenSync = () => {
  251. return find((m) => m._isOpen);
  252. };
  253. const getMenusSync = () => {
  254. return menus.map((menu) => menu.el);
  255. };
  256. const isAnimatingSync = () => {
  257. return menus.some((menu) => menu.isAnimating);
  258. };
  259. const find = (predicate) => {
  260. const instance = menus.find(predicate);
  261. if (instance !== undefined) {
  262. return instance.el;
  263. }
  264. return undefined;
  265. };
  266. const waitUntilReady = () => {
  267. return Promise.all(Array.from(document.querySelectorAll('ion-menu')).map((menu) => new Promise((resolve) => helpers.componentOnReady(menu, resolve))));
  268. };
  269. registerAnimation('reveal', menuRevealAnimation);
  270. registerAnimation('push', menuPushAnimation);
  271. registerAnimation('overlay', menuOverlayAnimation);
  272. index$1.doc === null || index$1.doc === void 0 ? void 0 : index$1.doc.addEventListener('ionBackButton', (ev) => {
  273. const openMenu = _getOpenSync();
  274. if (openMenu) {
  275. ev.detail.register(hardwareBackButton.MENU_BACK_BUTTON_PRIORITY, () => {
  276. return openMenu.close();
  277. });
  278. }
  279. });
  280. return {
  281. registerAnimation,
  282. get,
  283. getMenus,
  284. getOpen,
  285. isEnabled,
  286. swipeGesture,
  287. isAnimating,
  288. isOpen,
  289. enable,
  290. toggle,
  291. close,
  292. open,
  293. _getOpenSync,
  294. _createAnimation,
  295. _register,
  296. _unregister,
  297. _setOpen,
  298. };
  299. };
  300. const menuController = /*@__PURE__*/ createMenuController();
  301. exports.menuController = menuController;