ios.transition.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { c as createAnimation } from './animation.js';
  5. import { g as getIonPageElement } from './index2.js';
  6. const DURATION = 540;
  7. // TODO(FW-2832): types
  8. const getClonedElement = (tagName) => {
  9. return document.querySelector(`${tagName}.ion-cloned-element`);
  10. };
  11. const shadow = (el) => {
  12. return el.shadowRoot || el;
  13. };
  14. const getLargeTitle = (refEl) => {
  15. const tabs = refEl.tagName === 'ION-TABS' ? refEl : refEl.querySelector('ion-tabs');
  16. const query = 'ion-content ion-header:not(.header-collapse-condense-inactive) ion-title.title-large';
  17. if (tabs != null) {
  18. const activeTab = tabs.querySelector('ion-tab:not(.tab-hidden), .ion-page:not(.ion-page-hidden)');
  19. return activeTab != null ? activeTab.querySelector(query) : null;
  20. }
  21. return refEl.querySelector(query);
  22. };
  23. const getBackButton = (refEl, backDirection) => {
  24. const tabs = refEl.tagName === 'ION-TABS' ? refEl : refEl.querySelector('ion-tabs');
  25. let buttonsList = [];
  26. if (tabs != null) {
  27. const activeTab = tabs.querySelector('ion-tab:not(.tab-hidden), .ion-page:not(.ion-page-hidden)');
  28. if (activeTab != null) {
  29. buttonsList = activeTab.querySelectorAll('ion-buttons');
  30. }
  31. }
  32. else {
  33. buttonsList = refEl.querySelectorAll('ion-buttons');
  34. }
  35. for (const buttons of buttonsList) {
  36. const parentHeader = buttons.closest('ion-header');
  37. const activeHeader = parentHeader && !parentHeader.classList.contains('header-collapse-condense-inactive');
  38. const backButton = buttons.querySelector('ion-back-button');
  39. const buttonsCollapse = buttons.classList.contains('buttons-collapse');
  40. const startSlot = buttons.slot === 'start' || buttons.slot === '';
  41. if (backButton !== null && startSlot && ((buttonsCollapse && activeHeader && backDirection) || !buttonsCollapse)) {
  42. return backButton;
  43. }
  44. }
  45. return null;
  46. };
  47. const createLargeTitleTransition = (rootAnimation, rtl, backDirection, enteringEl, leavingEl) => {
  48. const enteringBackButton = getBackButton(enteringEl, backDirection);
  49. const leavingLargeTitle = getLargeTitle(leavingEl);
  50. const enteringLargeTitle = getLargeTitle(enteringEl);
  51. const leavingBackButton = getBackButton(leavingEl, backDirection);
  52. const shouldAnimationForward = enteringBackButton !== null && leavingLargeTitle !== null && !backDirection;
  53. const shouldAnimationBackward = enteringLargeTitle !== null && leavingBackButton !== null && backDirection;
  54. if (shouldAnimationForward) {
  55. const leavingLargeTitleBox = leavingLargeTitle.getBoundingClientRect();
  56. const enteringBackButtonBox = enteringBackButton.getBoundingClientRect();
  57. const enteringBackButtonTextEl = shadow(enteringBackButton).querySelector('.button-text');
  58. // Text element not rendered if developers pass text="" to the back button
  59. const enteringBackButtonTextBox = enteringBackButtonTextEl === null || enteringBackButtonTextEl === void 0 ? void 0 : enteringBackButtonTextEl.getBoundingClientRect();
  60. const leavingLargeTitleTextEl = shadow(leavingLargeTitle).querySelector('.toolbar-title');
  61. const leavingLargeTitleTextBox = leavingLargeTitleTextEl.getBoundingClientRect();
  62. animateLargeTitle(rootAnimation, rtl, backDirection, leavingLargeTitle, leavingLargeTitleBox, leavingLargeTitleTextBox, enteringBackButtonBox, enteringBackButtonTextEl, enteringBackButtonTextBox);
  63. animateBackButton(rootAnimation, rtl, backDirection, enteringBackButton, enteringBackButtonBox, enteringBackButtonTextEl, enteringBackButtonTextBox, leavingLargeTitle, leavingLargeTitleTextBox);
  64. }
  65. else if (shouldAnimationBackward) {
  66. const enteringLargeTitleBox = enteringLargeTitle.getBoundingClientRect();
  67. const leavingBackButtonBox = leavingBackButton.getBoundingClientRect();
  68. const leavingBackButtonTextEl = shadow(leavingBackButton).querySelector('.button-text');
  69. // Text element not rendered if developers pass text="" to the back button
  70. const leavingBackButtonTextBox = leavingBackButtonTextEl === null || leavingBackButtonTextEl === void 0 ? void 0 : leavingBackButtonTextEl.getBoundingClientRect();
  71. const enteringLargeTitleTextEl = shadow(enteringLargeTitle).querySelector('.toolbar-title');
  72. const enteringLargeTitleTextBox = enteringLargeTitleTextEl.getBoundingClientRect();
  73. animateLargeTitle(rootAnimation, rtl, backDirection, enteringLargeTitle, enteringLargeTitleBox, enteringLargeTitleTextBox, leavingBackButtonBox, leavingBackButtonTextEl, leavingBackButtonTextBox);
  74. animateBackButton(rootAnimation, rtl, backDirection, leavingBackButton, leavingBackButtonBox, leavingBackButtonTextEl, leavingBackButtonTextBox, enteringLargeTitle, enteringLargeTitleTextBox);
  75. }
  76. return {
  77. forward: shouldAnimationForward,
  78. backward: shouldAnimationBackward,
  79. };
  80. };
  81. const animateBackButton = (rootAnimation, rtl, backDirection, backButtonEl, backButtonBox, backButtonTextEl, backButtonTextBox, largeTitleEl, largeTitleTextBox) => {
  82. var _a, _b;
  83. const BACK_BUTTON_START_OFFSET = rtl ? `calc(100% - ${backButtonBox.right + 4}px)` : `${backButtonBox.left - 4}px`;
  84. const TEXT_ORIGIN_X = rtl ? 'right' : 'left';
  85. const ICON_ORIGIN_X = rtl ? 'left' : 'right';
  86. const CONTAINER_ORIGIN_X = rtl ? 'right' : 'left';
  87. let WIDTH_SCALE = 1;
  88. let HEIGHT_SCALE = 1;
  89. let TEXT_START_SCALE = `scale(${HEIGHT_SCALE})`;
  90. const TEXT_END_SCALE = 'scale(1)';
  91. if (backButtonTextEl && backButtonTextBox) {
  92. /**
  93. * When the title and back button texts match then they should overlap during the
  94. * page transition. If the texts do not match up then the back button text scale
  95. * adjusts to not perfectly match the large title text otherwise the proportions
  96. * will be incorrect. When the texts match we scale both the width and height to
  97. * account for font weight differences between the title and back button.
  98. */
  99. const doTitleAndButtonTextsMatch = ((_a = backButtonTextEl.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === ((_b = largeTitleEl.textContent) === null || _b === void 0 ? void 0 : _b.trim());
  100. WIDTH_SCALE = largeTitleTextBox.width / backButtonTextBox.width;
  101. /**
  102. * Subtract an offset to account for slight sizing/padding differences between the
  103. * title and the back button.
  104. */
  105. HEIGHT_SCALE = (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET) / backButtonTextBox.height;
  106. /**
  107. * Even though we set TEXT_START_SCALE to HEIGHT_SCALE above, we potentially need
  108. * to re-compute this here since the HEIGHT_SCALE may have changed.
  109. */
  110. TEXT_START_SCALE = doTitleAndButtonTextsMatch ? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})` : `scale(${HEIGHT_SCALE})`;
  111. }
  112. const backButtonIconEl = shadow(backButtonEl).querySelector('ion-icon');
  113. const backButtonIconBox = backButtonIconEl.getBoundingClientRect();
  114. /**
  115. * We need to offset the container by the icon dimensions
  116. * so that the back button text aligns with the large title
  117. * text. Otherwise, the back button icon will align with the
  118. * large title text but the back button text will not.
  119. */
  120. const CONTAINER_START_TRANSLATE_X = rtl
  121. ? `${backButtonIconBox.width / 2 - (backButtonIconBox.right - backButtonBox.right)}px`
  122. : `${backButtonBox.left - backButtonIconBox.width / 2}px`;
  123. const CONTAINER_END_TRANSLATE_X = rtl ? `-${window.innerWidth - backButtonBox.right}px` : `${backButtonBox.left}px`;
  124. /**
  125. * Back button container should be
  126. * aligned to the top of the title container
  127. * so the texts overlap as the back button
  128. * text begins to fade in.
  129. */
  130. const CONTAINER_START_TRANSLATE_Y = `${largeTitleTextBox.top}px`;
  131. /**
  132. * The cloned back button should align exactly with the
  133. * real back button on the entering page otherwise there will
  134. * be a layout shift.
  135. */
  136. const CONTAINER_END_TRANSLATE_Y = `${backButtonBox.top}px`;
  137. /**
  138. * In the forward direction, the cloned back button
  139. * container should translate from over the large title
  140. * to over the back button. In the backward direction,
  141. * it should translate from over the back button to over
  142. * the large title.
  143. */
  144. const FORWARD_CONTAINER_KEYFRAMES = [
  145. { offset: 0, transform: `translate3d(${CONTAINER_START_TRANSLATE_X}, ${CONTAINER_START_TRANSLATE_Y}, 0)` },
  146. { offset: 1, transform: `translate3d(${CONTAINER_END_TRANSLATE_X}, ${CONTAINER_END_TRANSLATE_Y}, 0)` },
  147. ];
  148. const BACKWARD_CONTAINER_KEYFRAMES = [
  149. { offset: 0, transform: `translate3d(${CONTAINER_END_TRANSLATE_X}, ${CONTAINER_END_TRANSLATE_Y}, 0)` },
  150. { offset: 1, transform: `translate3d(${CONTAINER_START_TRANSLATE_X}, ${CONTAINER_START_TRANSLATE_Y}, 0)` },
  151. ];
  152. const CONTAINER_KEYFRAMES = backDirection ? BACKWARD_CONTAINER_KEYFRAMES : FORWARD_CONTAINER_KEYFRAMES;
  153. /**
  154. * In the forward direction, the text in the cloned back button
  155. * should start to be (roughly) the size of the large title
  156. * and then scale down to be the size of the actual back button.
  157. * The text should also translate, but that translate is handled
  158. * by the container keyframes.
  159. */
  160. const FORWARD_TEXT_KEYFRAMES = [
  161. { offset: 0, opacity: 0, transform: TEXT_START_SCALE },
  162. { offset: 1, opacity: 1, transform: TEXT_END_SCALE },
  163. ];
  164. const BACKWARD_TEXT_KEYFRAMES = [
  165. { offset: 0, opacity: 1, transform: TEXT_END_SCALE },
  166. { offset: 1, opacity: 0, transform: TEXT_START_SCALE },
  167. ];
  168. const TEXT_KEYFRAMES = backDirection ? BACKWARD_TEXT_KEYFRAMES : FORWARD_TEXT_KEYFRAMES;
  169. /**
  170. * The icon should scale in/out in the second
  171. * half of the animation. The icon should also
  172. * translate, but that translate is handled by the
  173. * container keyframes.
  174. */
  175. const FORWARD_ICON_KEYFRAMES = [
  176. { offset: 0, opacity: 0, transform: 'scale(0.6)' },
  177. { offset: 0.6, opacity: 0, transform: 'scale(0.6)' },
  178. { offset: 1, opacity: 1, transform: 'scale(1)' },
  179. ];
  180. const BACKWARD_ICON_KEYFRAMES = [
  181. { offset: 0, opacity: 1, transform: 'scale(1)' },
  182. { offset: 0.2, opacity: 0, transform: 'scale(0.6)' },
  183. { offset: 1, opacity: 0, transform: 'scale(0.6)' },
  184. ];
  185. const ICON_KEYFRAMES = backDirection ? BACKWARD_ICON_KEYFRAMES : FORWARD_ICON_KEYFRAMES;
  186. const enteringBackButtonTextAnimation = createAnimation();
  187. const enteringBackButtonIconAnimation = createAnimation();
  188. const enteringBackButtonAnimation = createAnimation();
  189. const clonedBackButtonEl = getClonedElement('ion-back-button');
  190. const clonedBackButtonTextEl = shadow(clonedBackButtonEl).querySelector('.button-text');
  191. const clonedBackButtonIconEl = shadow(clonedBackButtonEl).querySelector('ion-icon');
  192. clonedBackButtonEl.text = backButtonEl.text;
  193. clonedBackButtonEl.mode = backButtonEl.mode;
  194. clonedBackButtonEl.icon = backButtonEl.icon;
  195. clonedBackButtonEl.color = backButtonEl.color;
  196. clonedBackButtonEl.disabled = backButtonEl.disabled;
  197. clonedBackButtonEl.style.setProperty('display', 'block');
  198. clonedBackButtonEl.style.setProperty('position', 'fixed');
  199. enteringBackButtonIconAnimation.addElement(clonedBackButtonIconEl);
  200. enteringBackButtonTextAnimation.addElement(clonedBackButtonTextEl);
  201. enteringBackButtonAnimation.addElement(clonedBackButtonEl);
  202. enteringBackButtonAnimation
  203. .beforeStyles({
  204. position: 'absolute',
  205. top: '0px',
  206. [CONTAINER_ORIGIN_X]: '0px',
  207. })
  208. /**
  209. * The write hooks must be set on this animation as it is guaranteed to run. Other
  210. * animations such as the back button text animation will not run if the back button
  211. * has no visible text.
  212. */
  213. .beforeAddWrite(() => {
  214. backButtonEl.style.setProperty('display', 'none');
  215. clonedBackButtonEl.style.setProperty(TEXT_ORIGIN_X, BACK_BUTTON_START_OFFSET);
  216. })
  217. .afterAddWrite(() => {
  218. backButtonEl.style.setProperty('display', '');
  219. clonedBackButtonEl.style.setProperty('display', 'none');
  220. clonedBackButtonEl.style.removeProperty(TEXT_ORIGIN_X);
  221. })
  222. .keyframes(CONTAINER_KEYFRAMES);
  223. enteringBackButtonTextAnimation
  224. .beforeStyles({
  225. 'transform-origin': `${TEXT_ORIGIN_X} top`,
  226. })
  227. .keyframes(TEXT_KEYFRAMES);
  228. enteringBackButtonIconAnimation
  229. .beforeStyles({
  230. 'transform-origin': `${ICON_ORIGIN_X} center`,
  231. })
  232. .keyframes(ICON_KEYFRAMES);
  233. rootAnimation.addAnimation([
  234. enteringBackButtonTextAnimation,
  235. enteringBackButtonIconAnimation,
  236. enteringBackButtonAnimation,
  237. ]);
  238. };
  239. const animateLargeTitle = (rootAnimation, rtl, backDirection, largeTitleEl, largeTitleBox, largeTitleTextBox, backButtonBox, backButtonTextEl, backButtonTextBox) => {
  240. var _a, _b;
  241. /**
  242. * The horizontal transform origin for the large title
  243. */
  244. const ORIGIN_X = rtl ? 'right' : 'left';
  245. const TITLE_START_OFFSET = rtl ? `calc(100% - ${largeTitleBox.right}px)` : `${largeTitleBox.left}px`;
  246. /**
  247. * The cloned large should align exactly with the
  248. * real large title on the leaving page otherwise there will
  249. * be a layout shift.
  250. */
  251. const START_TRANSLATE_X = '0px';
  252. const START_TRANSLATE_Y = `${largeTitleBox.top}px`;
  253. /**
  254. * How much to offset the large title translation by.
  255. * This accounts for differences in sizing between the large
  256. * title and the back button due to padding and font weight.
  257. */
  258. const LARGE_TITLE_TRANSLATION_OFFSET = 8;
  259. let END_TRANSLATE_X = rtl
  260. ? `-${window.innerWidth - backButtonBox.right - LARGE_TITLE_TRANSLATION_OFFSET}px`
  261. : `${backButtonBox.x + LARGE_TITLE_TRANSLATION_OFFSET}px`;
  262. /**
  263. * How much to scale the large title up/down by.
  264. */
  265. let HEIGHT_SCALE = 0.5;
  266. /**
  267. * The large title always starts full size.
  268. */
  269. const START_SCALE = 'scale(1)';
  270. /**
  271. * By default, we don't worry about having the large title scaled to perfectly
  272. * match the back button because we don't know if the back button's text matches
  273. * the large title's text.
  274. */
  275. let END_SCALE = `scale(${HEIGHT_SCALE})`;
  276. // Text element not rendered if developers pass text="" to the back button
  277. if (backButtonTextEl && backButtonTextBox) {
  278. /**
  279. * The scaled title should (roughly) overlap the back button. This ensures that
  280. * the back button and title overlap during the animation. Note that since both
  281. * elements either fade in or fade out over the course of the animation, neither
  282. * element will be fully visible on top of the other. As a result, the overlap
  283. * does not need to be perfect, so approximate values are acceptable here.
  284. */
  285. END_TRANSLATE_X = rtl
  286. ? `-${window.innerWidth - backButtonTextBox.right - LARGE_TITLE_TRANSLATION_OFFSET}px`
  287. : `${backButtonTextBox.x - LARGE_TITLE_TRANSLATION_OFFSET}px`;
  288. /**
  289. * In the forward direction, the large title should start at its normal size and
  290. * then scale down to be (roughly) the size of the back button on the other view.
  291. * In the backward direction, the large title should start at (roughly) the size
  292. * of the back button and then scale up to its original size.
  293. * Note that since both elements either fade in or fade out over the course of the
  294. * animation, neither element will be fully visible on top of the other. As a result,
  295. * the overlap does not need to be perfect, so approximate values are acceptable here.
  296. */
  297. /**
  298. * When the title and back button texts match then they should overlap during the
  299. * page transition. If the texts do not match up then the large title text scale
  300. * adjusts to not perfectly match the back button text otherwise the proportions
  301. * will be incorrect. When the texts match we scale both the width and height to
  302. * account for font weight differences between the title and back button.
  303. */
  304. const doTitleAndButtonTextsMatch = ((_a = backButtonTextEl.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === ((_b = largeTitleEl.textContent) === null || _b === void 0 ? void 0 : _b.trim());
  305. const WIDTH_SCALE = backButtonTextBox.width / largeTitleTextBox.width;
  306. HEIGHT_SCALE = backButtonTextBox.height / (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET);
  307. /**
  308. * Even though we set TEXT_START_SCALE to HEIGHT_SCALE above, we potentially need
  309. * to re-compute this here since the HEIGHT_SCALE may have changed.
  310. */
  311. END_SCALE = doTitleAndButtonTextsMatch ? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})` : `scale(${HEIGHT_SCALE})`;
  312. }
  313. /**
  314. * The midpoints of the back button and the title should align such that the back
  315. * button and title appear to be centered with each other.
  316. */
  317. const backButtonMidPoint = backButtonBox.top + backButtonBox.height / 2;
  318. const titleMidPoint = (largeTitleBox.height * HEIGHT_SCALE) / 2;
  319. const END_TRANSLATE_Y = `${backButtonMidPoint - titleMidPoint}px`;
  320. const BACKWARDS_KEYFRAMES = [
  321. { offset: 0, opacity: 0, transform: `translate3d(${END_TRANSLATE_X}, ${END_TRANSLATE_Y}, 0) ${END_SCALE}` },
  322. { offset: 0.1, opacity: 0 },
  323. { offset: 1, opacity: 1, transform: `translate3d(${START_TRANSLATE_X}, ${START_TRANSLATE_Y}, 0) ${START_SCALE}` },
  324. ];
  325. const FORWARDS_KEYFRAMES = [
  326. {
  327. offset: 0,
  328. opacity: 0.99,
  329. transform: `translate3d(${START_TRANSLATE_X}, ${START_TRANSLATE_Y}, 0) ${START_SCALE}`,
  330. },
  331. { offset: 0.6, opacity: 0 },
  332. { offset: 1, opacity: 0, transform: `translate3d(${END_TRANSLATE_X}, ${END_TRANSLATE_Y}, 0) ${END_SCALE}` },
  333. ];
  334. const KEYFRAMES = backDirection ? BACKWARDS_KEYFRAMES : FORWARDS_KEYFRAMES;
  335. const clonedTitleEl = getClonedElement('ion-title');
  336. const clonedLargeTitleAnimation = createAnimation();
  337. clonedTitleEl.innerText = largeTitleEl.innerText;
  338. clonedTitleEl.size = largeTitleEl.size;
  339. clonedTitleEl.color = largeTitleEl.color;
  340. clonedLargeTitleAnimation.addElement(clonedTitleEl);
  341. clonedLargeTitleAnimation
  342. .beforeStyles({
  343. 'transform-origin': `${ORIGIN_X} top`,
  344. /**
  345. * Since font size changes will cause
  346. * the dimension of the large title to change
  347. * we need to set the cloned title height
  348. * equal to that of the original large title height.
  349. */
  350. height: `${largeTitleBox.height}px`,
  351. display: '',
  352. position: 'relative',
  353. [ORIGIN_X]: TITLE_START_OFFSET,
  354. })
  355. .beforeAddWrite(() => {
  356. largeTitleEl.style.setProperty('opacity', '0');
  357. })
  358. .afterAddWrite(() => {
  359. largeTitleEl.style.setProperty('opacity', '');
  360. clonedTitleEl.style.setProperty('display', 'none');
  361. })
  362. .keyframes(KEYFRAMES);
  363. rootAnimation.addAnimation(clonedLargeTitleAnimation);
  364. };
  365. const iosTransitionAnimation = (navEl, opts) => {
  366. var _a;
  367. try {
  368. const EASING = 'cubic-bezier(0.32,0.72,0,1)';
  369. const OPACITY = 'opacity';
  370. const TRANSFORM = 'transform';
  371. const CENTER = '0%';
  372. const OFF_OPACITY = 0.8;
  373. const isRTL = navEl.ownerDocument.dir === 'rtl';
  374. const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
  375. const OFF_LEFT = isRTL ? '33%' : '-33%';
  376. const enteringEl = opts.enteringEl;
  377. const leavingEl = opts.leavingEl;
  378. const backDirection = opts.direction === 'back';
  379. const contentEl = enteringEl.querySelector(':scope > ion-content');
  380. const headerEls = enteringEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
  381. const enteringToolBarEls = enteringEl.querySelectorAll(':scope > ion-header > ion-toolbar');
  382. const rootAnimation = createAnimation();
  383. const enteringContentAnimation = createAnimation();
  384. rootAnimation
  385. .addElement(enteringEl)
  386. .duration(((_a = opts.duration) !== null && _a !== void 0 ? _a : 0) || DURATION)
  387. .easing(opts.easing || EASING)
  388. .fill('both')
  389. .beforeRemoveClass('ion-page-invisible');
  390. // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
  391. if (leavingEl && navEl !== null && navEl !== undefined) {
  392. const navDecorAnimation = createAnimation();
  393. navDecorAnimation.addElement(navEl);
  394. rootAnimation.addAnimation(navDecorAnimation);
  395. }
  396. if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) {
  397. enteringContentAnimation.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs')); // REVIEW
  398. }
  399. else {
  400. enteringContentAnimation.addElement(contentEl); // REVIEW
  401. enteringContentAnimation.addElement(headerEls);
  402. }
  403. rootAnimation.addAnimation(enteringContentAnimation);
  404. if (backDirection) {
  405. enteringContentAnimation
  406. .beforeClearStyles([OPACITY])
  407. .fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`)
  408. .fromTo(OPACITY, OFF_OPACITY, 1);
  409. }
  410. else {
  411. // entering content, forward direction
  412. enteringContentAnimation
  413. .beforeClearStyles([OPACITY])
  414. .fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
  415. }
  416. if (contentEl) {
  417. const enteringTransitionEffectEl = shadow(contentEl).querySelector('.transition-effect');
  418. if (enteringTransitionEffectEl) {
  419. const enteringTransitionCoverEl = enteringTransitionEffectEl.querySelector('.transition-cover');
  420. const enteringTransitionShadowEl = enteringTransitionEffectEl.querySelector('.transition-shadow');
  421. const enteringTransitionEffect = createAnimation();
  422. const enteringTransitionCover = createAnimation();
  423. const enteringTransitionShadow = createAnimation();
  424. enteringTransitionEffect
  425. .addElement(enteringTransitionEffectEl)
  426. .beforeStyles({ opacity: '1', display: 'block' })
  427. .afterStyles({ opacity: '', display: '' });
  428. enteringTransitionCover
  429. .addElement(enteringTransitionCoverEl) // REVIEW
  430. .beforeClearStyles([OPACITY])
  431. .fromTo(OPACITY, 0, 0.1);
  432. enteringTransitionShadow
  433. .addElement(enteringTransitionShadowEl) // REVIEW
  434. .beforeClearStyles([OPACITY])
  435. .fromTo(OPACITY, 0.03, 0.7);
  436. enteringTransitionEffect.addAnimation([enteringTransitionCover, enteringTransitionShadow]);
  437. enteringContentAnimation.addAnimation([enteringTransitionEffect]);
  438. }
  439. }
  440. const enteringContentHasLargeTitle = enteringEl.querySelector('ion-header.header-collapse-condense');
  441. const { forward, backward } = createLargeTitleTransition(rootAnimation, isRTL, backDirection, enteringEl, leavingEl);
  442. enteringToolBarEls.forEach((enteringToolBarEl) => {
  443. const enteringToolBar = createAnimation();
  444. enteringToolBar.addElement(enteringToolBarEl);
  445. rootAnimation.addAnimation(enteringToolBar);
  446. const enteringTitle = createAnimation();
  447. enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title')); // REVIEW
  448. const enteringToolBarButtons = createAnimation();
  449. const buttons = Array.from(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
  450. const parentHeader = enteringToolBarEl.closest('ion-header');
  451. const inactiveHeader = parentHeader === null || parentHeader === void 0 ? void 0 : parentHeader.classList.contains('header-collapse-condense-inactive');
  452. let buttonsToAnimate;
  453. if (backDirection) {
  454. buttonsToAnimate = buttons.filter((button) => {
  455. const isCollapseButton = button.classList.contains('buttons-collapse');
  456. return (isCollapseButton && !inactiveHeader) || !isCollapseButton;
  457. });
  458. }
  459. else {
  460. buttonsToAnimate = buttons.filter((button) => !button.classList.contains('buttons-collapse'));
  461. }
  462. enteringToolBarButtons.addElement(buttonsToAnimate);
  463. const enteringToolBarItems = createAnimation();
  464. enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'));
  465. const enteringToolBarBg = createAnimation();
  466. enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background')); // REVIEW
  467. const enteringBackButton = createAnimation();
  468. const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
  469. if (backButtonEl) {
  470. enteringBackButton.addElement(backButtonEl);
  471. }
  472. enteringToolBar.addAnimation([
  473. enteringTitle,
  474. enteringToolBarButtons,
  475. enteringToolBarItems,
  476. enteringToolBarBg,
  477. enteringBackButton,
  478. ]);
  479. enteringToolBarButtons.fromTo(OPACITY, 0.01, 1);
  480. enteringToolBarItems.fromTo(OPACITY, 0.01, 1);
  481. if (backDirection) {
  482. if (!inactiveHeader) {
  483. enteringTitle
  484. .fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`)
  485. .fromTo(OPACITY, 0.01, 1);
  486. }
  487. enteringToolBarItems.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
  488. // back direction, entering page has a back button
  489. enteringBackButton.fromTo(OPACITY, 0.01, 1);
  490. }
  491. else {
  492. // entering toolbar, forward direction
  493. if (!enteringContentHasLargeTitle) {
  494. enteringTitle
  495. .fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`)
  496. .fromTo(OPACITY, 0.01, 1);
  497. }
  498. enteringToolBarItems.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
  499. enteringToolBarBg.beforeClearStyles([OPACITY, 'transform']);
  500. const translucentHeader = parentHeader === null || parentHeader === void 0 ? void 0 : parentHeader.translucent;
  501. if (!translucentHeader) {
  502. enteringToolBarBg.fromTo(OPACITY, 0.01, 'var(--opacity)');
  503. }
  504. else {
  505. enteringToolBarBg.fromTo('transform', isRTL ? 'translateX(-100%)' : 'translateX(100%)', 'translateX(0px)');
  506. }
  507. // forward direction, entering page has a back button
  508. if (!forward) {
  509. enteringBackButton.fromTo(OPACITY, 0.01, 1);
  510. }
  511. if (backButtonEl && !forward) {
  512. const enteringBackBtnText = createAnimation();
  513. enteringBackBtnText
  514. .addElement(shadow(backButtonEl).querySelector('.button-text')) // REVIEW
  515. .fromTo(`transform`, isRTL ? 'translateX(-100px)' : 'translateX(100px)', 'translateX(0px)');
  516. enteringToolBar.addAnimation(enteringBackBtnText);
  517. }
  518. }
  519. });
  520. // setup leaving view
  521. if (leavingEl) {
  522. const leavingContent = createAnimation();
  523. const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
  524. const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
  525. const leavingHeaderEls = leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
  526. if (!leavingContentEl && leavingToolBarEls.length === 0 && leavingHeaderEls.length === 0) {
  527. leavingContent.addElement(leavingEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs')); // REVIEW
  528. }
  529. else {
  530. leavingContent.addElement(leavingContentEl); // REVIEW
  531. leavingContent.addElement(leavingHeaderEls);
  532. }
  533. rootAnimation.addAnimation(leavingContent);
  534. if (backDirection) {
  535. // leaving content, back direction
  536. leavingContent
  537. .beforeClearStyles([OPACITY])
  538. .fromTo('transform', `translateX(${CENTER})`, isRTL ? 'translateX(-100%)' : 'translateX(100%)');
  539. const leavingPage = getIonPageElement(leavingEl);
  540. rootAnimation.afterAddWrite(() => {
  541. if (rootAnimation.getDirection() === 'normal') {
  542. leavingPage.style.setProperty('display', 'none');
  543. }
  544. });
  545. }
  546. else {
  547. // leaving content, forward direction
  548. leavingContent
  549. .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
  550. .fromTo(OPACITY, 1, OFF_OPACITY);
  551. }
  552. if (leavingContentEl) {
  553. const leavingTransitionEffectEl = shadow(leavingContentEl).querySelector('.transition-effect');
  554. if (leavingTransitionEffectEl) {
  555. const leavingTransitionCoverEl = leavingTransitionEffectEl.querySelector('.transition-cover');
  556. const leavingTransitionShadowEl = leavingTransitionEffectEl.querySelector('.transition-shadow');
  557. const leavingTransitionEffect = createAnimation();
  558. const leavingTransitionCover = createAnimation();
  559. const leavingTransitionShadow = createAnimation();
  560. leavingTransitionEffect
  561. .addElement(leavingTransitionEffectEl)
  562. .beforeStyles({ opacity: '1', display: 'block' })
  563. .afterStyles({ opacity: '', display: '' });
  564. leavingTransitionCover
  565. .addElement(leavingTransitionCoverEl) // REVIEW
  566. .beforeClearStyles([OPACITY])
  567. .fromTo(OPACITY, 0.1, 0);
  568. leavingTransitionShadow
  569. .addElement(leavingTransitionShadowEl) // REVIEW
  570. .beforeClearStyles([OPACITY])
  571. .fromTo(OPACITY, 0.7, 0.03);
  572. leavingTransitionEffect.addAnimation([leavingTransitionCover, leavingTransitionShadow]);
  573. leavingContent.addAnimation([leavingTransitionEffect]);
  574. }
  575. }
  576. leavingToolBarEls.forEach((leavingToolBarEl) => {
  577. const leavingToolBar = createAnimation();
  578. leavingToolBar.addElement(leavingToolBarEl);
  579. const leavingTitle = createAnimation();
  580. leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title')); // REVIEW
  581. const leavingToolBarButtons = createAnimation();
  582. const buttons = leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]');
  583. const parentHeader = leavingToolBarEl.closest('ion-header');
  584. const inactiveHeader = parentHeader === null || parentHeader === void 0 ? void 0 : parentHeader.classList.contains('header-collapse-condense-inactive');
  585. const buttonsToAnimate = Array.from(buttons).filter((button) => {
  586. const isCollapseButton = button.classList.contains('buttons-collapse');
  587. return (isCollapseButton && !inactiveHeader) || !isCollapseButton;
  588. });
  589. leavingToolBarButtons.addElement(buttonsToAnimate);
  590. const leavingToolBarItems = createAnimation();
  591. const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
  592. if (leavingToolBarItemEls.length > 0) {
  593. leavingToolBarItems.addElement(leavingToolBarItemEls);
  594. }
  595. const leavingToolBarBg = createAnimation();
  596. leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background')); // REVIEW
  597. const leavingBackButton = createAnimation();
  598. const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
  599. if (backButtonEl) {
  600. leavingBackButton.addElement(backButtonEl);
  601. }
  602. leavingToolBar.addAnimation([
  603. leavingTitle,
  604. leavingToolBarButtons,
  605. leavingToolBarItems,
  606. leavingBackButton,
  607. leavingToolBarBg,
  608. ]);
  609. rootAnimation.addAnimation(leavingToolBar);
  610. // fade out leaving toolbar items
  611. leavingBackButton.fromTo(OPACITY, 0.99, 0);
  612. leavingToolBarButtons.fromTo(OPACITY, 0.99, 0);
  613. leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
  614. if (backDirection) {
  615. if (!inactiveHeader) {
  616. // leaving toolbar, back direction
  617. leavingTitle
  618. .fromTo('transform', `translateX(${CENTER})`, isRTL ? 'translateX(-100%)' : 'translateX(100%)')
  619. .fromTo(OPACITY, 0.99, 0);
  620. }
  621. leavingToolBarItems.fromTo('transform', `translateX(${CENTER})`, isRTL ? 'translateX(-100%)' : 'translateX(100%)');
  622. leavingToolBarBg.beforeClearStyles([OPACITY, 'transform']);
  623. // leaving toolbar, back direction, and there's no entering toolbar
  624. // should just slide out, no fading out
  625. const translucentHeader = parentHeader === null || parentHeader === void 0 ? void 0 : parentHeader.translucent;
  626. if (!translucentHeader) {
  627. leavingToolBarBg.fromTo(OPACITY, 'var(--opacity)', 0);
  628. }
  629. else {
  630. leavingToolBarBg.fromTo('transform', 'translateX(0px)', isRTL ? 'translateX(-100%)' : 'translateX(100%)');
  631. }
  632. if (backButtonEl && !backward) {
  633. const leavingBackBtnText = createAnimation();
  634. leavingBackBtnText
  635. .addElement(shadow(backButtonEl).querySelector('.button-text')) // REVIEW
  636. .fromTo('transform', `translateX(${CENTER})`, `translateX(${(isRTL ? -124 : 124) + 'px'})`);
  637. leavingToolBar.addAnimation(leavingBackBtnText);
  638. }
  639. }
  640. else {
  641. // leaving toolbar, forward direction
  642. if (!inactiveHeader) {
  643. leavingTitle
  644. .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
  645. .fromTo(OPACITY, 0.99, 0)
  646. .afterClearStyles([TRANSFORM, OPACITY]);
  647. }
  648. leavingToolBarItems
  649. .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
  650. .afterClearStyles([TRANSFORM, OPACITY]);
  651. leavingBackButton.afterClearStyles([OPACITY]);
  652. leavingTitle.afterClearStyles([OPACITY]);
  653. leavingToolBarButtons.afterClearStyles([OPACITY]);
  654. }
  655. });
  656. }
  657. return rootAnimation;
  658. }
  659. catch (err) {
  660. throw err;
  661. }
  662. };
  663. /**
  664. * The scale of the back button during the animation
  665. * is computed based on the scale of the large title
  666. * and vice versa. However, we need to account for slight
  667. * variations in the size of the large title due to
  668. * padding and font weight. This value should be used to subtract
  669. * a small amount from the large title height when computing scales
  670. * to get more accurate scale results.
  671. */
  672. const LARGE_TITLE_SIZE_OFFSET = 10;
  673. export { iosTransitionAnimation, shadow };