ion-modal.entry.js 89 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { r as registerInstance, c as createEvent, w as writeTask, h, e as Host, f as getElement } from './index-527b9e34.js';
  5. import { f as findClosestIonContent, i as isIonContent, d as disableContentScrollY, r as resetContentScrollY, a as findIonContent, p as printIonContentErrorMsg } from './index-9a17db3d.js';
  6. import { C as CoreDelegate, a as attachComponent, d as detachComponent } from './framework-delegate-56b467ad.js';
  7. import { g as getElementRoot, j as clamp, r as raf, h as inheritAttributes, k as hasLazyBuild } from './helpers-d94bc8ad.js';
  8. import { c as createLockController } from './lock-controller-316928be.js';
  9. import { p as printIonWarning, c as config } from './index-cfd9c1f2.js';
  10. import { g as getCapacitor } from './capacitor-59395cbd.js';
  11. import { G as GESTURE, O as OVERLAY_GESTURE_PRIORITY, F as FOCUS_TRAP_DISABLE_CLASS, e as createTriggerController, B as BACKDROP, j as prepareOverlay, k as setOverlayId, f as present, g as dismiss, h as eventMethod } from './overlays-d99dcb0a.js';
  12. import { g as getClassMap } from './theme-01f3f29c.js';
  13. import { e as deepReady, w as waitForMount } from './index-68c0d151.js';
  14. import { b as getIonMode } from './ionic-global-b26f573e.js';
  15. import { KEYBOARD_DID_OPEN } from './keyboard-52278bd7.js';
  16. import { c as createAnimation } from './animation-8b25e105.js';
  17. import { g as getTimeGivenProgression } from './cubic-bezier-fe2083dc.js';
  18. import { createGesture } from './index-39782642.js';
  19. import { w as win } from './index-a5d50daf.js';
  20. import './hardware-back-button-a7eb8233.js';
  21. import './gesture-controller-314a54f6.js';
  22. import './keyboard-73175e24.js';
  23. var Style;
  24. (function (Style) {
  25. Style["Dark"] = "DARK";
  26. Style["Light"] = "LIGHT";
  27. Style["Default"] = "DEFAULT";
  28. })(Style || (Style = {}));
  29. const StatusBar = {
  30. getEngine() {
  31. const capacitor = getCapacitor();
  32. if (capacitor === null || capacitor === void 0 ? void 0 : capacitor.isPluginAvailable('StatusBar')) {
  33. return capacitor.Plugins.StatusBar;
  34. }
  35. return undefined;
  36. },
  37. setStyle(options) {
  38. const engine = this.getEngine();
  39. if (!engine) {
  40. return;
  41. }
  42. engine.setStyle(options);
  43. },
  44. getStyle: async function () {
  45. const engine = this.getEngine();
  46. if (!engine) {
  47. return Style.Default;
  48. }
  49. const { style } = await engine.getInfo();
  50. return style;
  51. },
  52. };
  53. /**
  54. * Use y = mx + b to
  55. * figure out the backdrop value
  56. * at a particular x coordinate. This
  57. * is useful when the backdrop does
  58. * not begin to fade in until after
  59. * the 0 breakpoint.
  60. */
  61. const getBackdropValueForSheet = (x, backdropBreakpoint) => {
  62. /**
  63. * We will use these points:
  64. * (backdropBreakpoint, 0)
  65. * (maxBreakpoint, 1)
  66. * We know that at the beginning breakpoint,
  67. * the backdrop will be hidden. We also
  68. * know that at the maxBreakpoint, the backdrop
  69. * must be fully visible. maxBreakpoint should
  70. * always be 1 even if the maximum value
  71. * of the breakpoints array is not 1 since
  72. * the animation runs from a progress of 0
  73. * to a progress of 1.
  74. * m = (y2 - y1) / (x2 - x1)
  75. *
  76. * This is simplified from:
  77. * m = (1 - 0) / (maxBreakpoint - backdropBreakpoint)
  78. *
  79. * If the backdropBreakpoint is 1, we return 0 as the
  80. * backdrop is completely hidden.
  81. *
  82. */
  83. if (backdropBreakpoint === 1) {
  84. return 0;
  85. }
  86. const slope = 1 / (1 - backdropBreakpoint);
  87. /**
  88. * From here, compute b which is
  89. * the backdrop opacity if the offset
  90. * is 0. If the backdrop does not
  91. * begin to fade in until after the
  92. * 0 breakpoint, this b value will be
  93. * negative. This is fine as we never pass
  94. * b directly into the animation keyframes.
  95. * b = y - mx
  96. * Use a known point: (backdropBreakpoint, 0)
  97. * This is simplified from:
  98. * b = 0 - (backdropBreakpoint * slope)
  99. */
  100. const b = -(backdropBreakpoint * slope);
  101. /**
  102. * Finally, we can now determine the
  103. * backdrop offset given an arbitrary
  104. * gesture offset.
  105. */
  106. return x * slope + b;
  107. };
  108. /**
  109. * The tablet/desktop card modal activates
  110. * when the window width is >= 768.
  111. * At that point, the presenting element
  112. * is not transformed, so we do not need to
  113. * adjust the status bar color.
  114. *
  115. */
  116. const setCardStatusBarDark = () => {
  117. if (!win || win.innerWidth >= 768) {
  118. return;
  119. }
  120. StatusBar.setStyle({ style: Style.Dark });
  121. };
  122. const setCardStatusBarDefault = (defaultStyle = Style.Default) => {
  123. if (!win || win.innerWidth >= 768) {
  124. return;
  125. }
  126. StatusBar.setStyle({ style: defaultStyle });
  127. };
  128. const handleCanDismiss = async (el, animation) => {
  129. /**
  130. * If canDismiss is not a function
  131. * then we can return early. If canDismiss is `true`,
  132. * then canDismissBlocksGesture is `false` as canDismiss
  133. * will never interrupt the gesture. As a result,
  134. * this code block is never reached. If canDismiss is `false`,
  135. * then we never dismiss.
  136. */
  137. if (typeof el.canDismiss !== 'function') {
  138. return;
  139. }
  140. /**
  141. * Run the canDismiss callback.
  142. * If the function returns `true`,
  143. * then we can proceed with dismiss.
  144. */
  145. const shouldDismiss = await el.canDismiss(undefined, GESTURE);
  146. if (!shouldDismiss) {
  147. return;
  148. }
  149. /**
  150. * If canDismiss resolved after the snap
  151. * back animation finished, we can
  152. * dismiss immediately.
  153. *
  154. * If canDismiss resolved before the snap
  155. * back animation finished, we need to
  156. * wait until the snap back animation is
  157. * done before dismissing.
  158. */
  159. if (animation.isRunning()) {
  160. animation.onFinish(() => {
  161. el.dismiss(undefined, 'handler');
  162. }, { oneTimeCallback: true });
  163. }
  164. else {
  165. el.dismiss(undefined, 'handler');
  166. }
  167. };
  168. /**
  169. * This function lets us simulate a realistic spring-like animation
  170. * when swiping down on the modal.
  171. * There are two forces that we need to use to compute the spring physics:
  172. *
  173. * 1. Stiffness, k: This is a measure of resistance applied a spring.
  174. * 2. Dampening, c: This value has the effect of reducing or preventing oscillation.
  175. *
  176. * Using these two values, we can calculate the Spring Force and the Dampening Force
  177. * to compute the total force applied to a spring.
  178. *
  179. * Spring Force: This force pulls a spring back into its equilibrium position.
  180. * Hooke's Law tells us that that spring force (FS) = kX.
  181. * k is the stiffness of a spring, and X is the displacement of the spring from its
  182. * equilibrium position. In this case, it is the amount by which the free end
  183. * of a spring was displaced (stretched/pushed) from its "relaxed" position.
  184. *
  185. * Dampening Force: This force slows down motion. Without it, a spring would oscillate forever.
  186. * The dampening force, FD, can be found via this formula: FD = -cv
  187. * where c the dampening value and v is velocity.
  188. *
  189. * Therefore, the resulting force that is exerted on the block is:
  190. * F = FS + FD = -kX - cv
  191. *
  192. * Newton's 2nd Law tells us that F = ma:
  193. * ma = -kX - cv.
  194. *
  195. * For Ionic's purposes, we can assume that m = 1:
  196. * a = -kX - cv
  197. *
  198. * Imagine a block attached to the end of a spring. At equilibrium
  199. * the block is at position x = 1.
  200. * Pressing on the block moves it to position x = 0;
  201. * So, to calculate the displacement, we need to take the
  202. * current position and subtract the previous position from it.
  203. * X = x - x0 = 0 - 1 = -1.
  204. *
  205. * For Ionic's purposes, we are only pushing on the spring modal
  206. * so we have a max position of 1.
  207. * As a result, we can expand displacement to this formula:
  208. * X = x - 1
  209. *
  210. * a = -k(x - 1) - cv
  211. *
  212. * We can represent the motion of something as a function of time: f(t) = x.
  213. * The derivative of position gives us the velocity: f'(t)
  214. * The derivative of the velocity gives us the acceleration: f''(t)
  215. *
  216. * We can substitute the formula above with these values:
  217. *
  218. * f"(t) = -k * (f(t) - 1) - c * f'(t)
  219. *
  220. * This is called a differential equation.
  221. *
  222. * We know that at t = 0, we are at x = 0 because the modal does not move: f(0) = 0
  223. * This means our velocity is also zero: f'(0) = 0.
  224. *
  225. * We can cheat a bit and plug the formula into Wolfram Alpha.
  226. * However, we need to pick stiffness and dampening values:
  227. * k = 0.57
  228. * c = 15
  229. *
  230. * I picked these as they are fairly close to native iOS's spring effect
  231. * with the modal.
  232. *
  233. * What we plug in is this: f(0) = 0; f'(0) = 0; f''(t) = -0.57(f(t) - 1) - 15f'(t)
  234. *
  235. * The result is a formula that lets us calculate the acceleration
  236. * for a given time t.
  237. * Note: This is the approximate form of the solution. Wolfram Alpha will
  238. * give you a complex differential equation too.
  239. */
  240. const calculateSpringStep = (t) => {
  241. return 0.00255275 * 2.71828 ** (-14.9619 * t) - 1.00255 * 2.71828 ** (-0.0380968 * t) + 1;
  242. };
  243. // Defaults for the card swipe animation
  244. const SwipeToCloseDefaults = {
  245. MIN_PRESENTING_SCALE: 0.915,
  246. };
  247. const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
  248. /**
  249. * The step value at which a card modal
  250. * is eligible for dismissing via gesture.
  251. */
  252. const DISMISS_THRESHOLD = 0.5;
  253. const height = el.offsetHeight;
  254. let isOpen = false;
  255. let canDismissBlocksGesture = false;
  256. let contentEl = null;
  257. let scrollEl = null;
  258. const canDismissMaxStep = 0.2;
  259. let initialScrollY = true;
  260. let lastStep = 0;
  261. const getScrollY = () => {
  262. if (contentEl && isIonContent(contentEl)) {
  263. return contentEl.scrollY;
  264. /**
  265. * Custom scroll containers are intended to be
  266. * used with virtual scrolling, so we assume
  267. * there is scrolling in this case.
  268. */
  269. }
  270. else {
  271. return true;
  272. }
  273. };
  274. const canStart = (detail) => {
  275. const target = detail.event.target;
  276. if (target === null || !target.closest) {
  277. return true;
  278. }
  279. /**
  280. * If we are swiping on the content,
  281. * swiping should only be possible if
  282. * the content is scrolled all the way
  283. * to the top so that we do not interfere
  284. * with scrolling.
  285. *
  286. * We cannot assume that the `ion-content`
  287. * target will remain consistent between
  288. * swipes. For example, when using
  289. * ion-nav within a card modal it is
  290. * possible to swipe, push a view, and then
  291. * swipe again. The target content will not
  292. * be the same between swipes.
  293. */
  294. contentEl = findClosestIonContent(target);
  295. if (contentEl) {
  296. /**
  297. * The card should never swipe to close
  298. * on the content with a refresher.
  299. * Note: We cannot solve this by making the
  300. * swipeToClose gesture have a higher priority
  301. * than the refresher gesture as the iOS native
  302. * refresh gesture uses a scroll listener in
  303. * addition to a gesture.
  304. *
  305. * Note: Do not use getScrollElement here
  306. * because we need this to be a synchronous
  307. * operation, and getScrollElement is
  308. * asynchronous.
  309. */
  310. if (isIonContent(contentEl)) {
  311. const root = getElementRoot(contentEl);
  312. scrollEl = root.querySelector('.inner-scroll');
  313. }
  314. else {
  315. scrollEl = contentEl;
  316. }
  317. const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
  318. return !hasRefresherInContent && scrollEl.scrollTop === 0;
  319. }
  320. /**
  321. * Card should be swipeable on all
  322. * parts of the modal except for the footer.
  323. */
  324. const footer = target.closest('ion-footer');
  325. if (footer === null) {
  326. return true;
  327. }
  328. return false;
  329. };
  330. const onStart = (detail) => {
  331. const { deltaY } = detail;
  332. /**
  333. * Get the initial scrollY value so
  334. * that we can correctly reset the scrollY
  335. * prop when the gesture ends.
  336. */
  337. initialScrollY = getScrollY();
  338. /**
  339. * If canDismiss is anything other than `true`
  340. * then users should be able to swipe down
  341. * until a threshold is hit. At that point,
  342. * the card modal should not proceed any further.
  343. * TODO (FW-937)
  344. * Remove undefined check
  345. */
  346. canDismissBlocksGesture = el.canDismiss !== undefined && el.canDismiss !== true;
  347. /**
  348. * If we are pulling down, then
  349. * it is possible we are pulling on the
  350. * content. We do not want scrolling to
  351. * happen at the same time as the gesture.
  352. */
  353. if (deltaY > 0 && contentEl) {
  354. disableContentScrollY(contentEl);
  355. }
  356. animation.progressStart(true, isOpen ? 1 : 0);
  357. };
  358. const onMove = (detail) => {
  359. const { deltaY } = detail;
  360. /**
  361. * If we are pulling down, then
  362. * it is possible we are pulling on the
  363. * content. We do not want scrolling to
  364. * happen at the same time as the gesture.
  365. */
  366. if (deltaY > 0 && contentEl) {
  367. disableContentScrollY(contentEl);
  368. }
  369. /**
  370. * If we are swiping on the content
  371. * then the swipe gesture should only
  372. * happen if we are pulling down.
  373. *
  374. * However, if we pull up and
  375. * then down such that the scroll position
  376. * returns to 0, we should be able to swipe
  377. * the card.
  378. */
  379. const step = detail.deltaY / height;
  380. /**
  381. * Check if user is swiping down and
  382. * if we have a canDismiss value that
  383. * should block the gesture from
  384. * proceeding,
  385. */
  386. const isAttemptingDismissWithCanDismiss = step >= 0 && canDismissBlocksGesture;
  387. /**
  388. * If we are blocking the gesture from dismissing,
  389. * set the max step value so that the sheet cannot be
  390. * completely hidden.
  391. */
  392. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  393. /**
  394. * If we are blocking the gesture from
  395. * dismissing, calculate the spring modifier value
  396. * this will be added to the starting breakpoint
  397. * value to give the gesture a spring-like feeling.
  398. * Note that the starting breakpoint is always 0,
  399. * so we omit adding 0 to the result.
  400. */
  401. const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
  402. const clampedStep = clamp(0.0001, processedStep, maxStep);
  403. animation.progressStep(clampedStep);
  404. /**
  405. * When swiping down half way, the status bar style
  406. * should be reset to its default value.
  407. *
  408. * We track lastStep so that we do not fire these
  409. * functions on every onMove, only when the user has
  410. * crossed a certain threshold.
  411. */
  412. if (clampedStep >= DISMISS_THRESHOLD && lastStep < DISMISS_THRESHOLD) {
  413. setCardStatusBarDefault(statusBarStyle);
  414. /**
  415. * However, if we swipe back up, then the
  416. * status bar style should be set to have light
  417. * text on a dark background.
  418. */
  419. }
  420. else if (clampedStep < DISMISS_THRESHOLD && lastStep >= DISMISS_THRESHOLD) {
  421. setCardStatusBarDark();
  422. }
  423. lastStep = clampedStep;
  424. };
  425. const onEnd = (detail) => {
  426. const velocity = detail.velocityY;
  427. const step = detail.deltaY / height;
  428. const isAttemptingDismissWithCanDismiss = step >= 0 && canDismissBlocksGesture;
  429. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  430. const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
  431. const clampedStep = clamp(0.0001, processedStep, maxStep);
  432. const threshold = (detail.deltaY + velocity * 1000) / height;
  433. /**
  434. * If canDismiss blocks
  435. * the swipe gesture, then the
  436. * animation can never complete until
  437. * canDismiss is checked.
  438. */
  439. const shouldComplete = !isAttemptingDismissWithCanDismiss && threshold >= DISMISS_THRESHOLD;
  440. let newStepValue = shouldComplete ? -0.001 : 0.001;
  441. if (!shouldComplete) {
  442. animation.easing('cubic-bezier(1, 0, 0.68, 0.28)');
  443. newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], clampedStep)[0];
  444. }
  445. else {
  446. animation.easing('cubic-bezier(0.32, 0.72, 0, 1)');
  447. newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], clampedStep)[0];
  448. }
  449. const duration = shouldComplete
  450. ? computeDuration(step * height, velocity)
  451. : computeDuration((1 - clampedStep) * height, velocity);
  452. isOpen = shouldComplete;
  453. gesture.enable(false);
  454. if (contentEl) {
  455. resetContentScrollY(contentEl, initialScrollY);
  456. }
  457. animation
  458. .onFinish(() => {
  459. if (!shouldComplete) {
  460. gesture.enable(true);
  461. }
  462. })
  463. .progressEnd(shouldComplete ? 1 : 0, newStepValue, duration);
  464. /**
  465. * If the canDismiss value blocked the gesture
  466. * from proceeding, then we should ignore whatever
  467. * shouldComplete is. Whether or not the modal
  468. * animation should complete is now determined by
  469. * canDismiss.
  470. *
  471. * If the user swiped >25% of the way
  472. * to the max step, then we should
  473. * check canDismiss. 25% was chosen
  474. * to avoid accidental swipes.
  475. */
  476. if (isAttemptingDismissWithCanDismiss && clampedStep > maxStep / 4) {
  477. handleCanDismiss(el, animation);
  478. }
  479. else if (shouldComplete) {
  480. onDismiss();
  481. }
  482. };
  483. const gesture = createGesture({
  484. el,
  485. gestureName: 'modalSwipeToClose',
  486. gesturePriority: OVERLAY_GESTURE_PRIORITY,
  487. direction: 'y',
  488. threshold: 10,
  489. canStart,
  490. onStart,
  491. onMove,
  492. onEnd,
  493. });
  494. return gesture;
  495. };
  496. const computeDuration = (remaining, velocity) => {
  497. return clamp(400, remaining / Math.abs(velocity * 1.1), 500);
  498. };
  499. const createSheetEnterAnimation = (opts) => {
  500. const { currentBreakpoint, backdropBreakpoint, expandToScroll } = opts;
  501. /**
  502. * If the backdropBreakpoint is undefined, then the backdrop
  503. * should always fade in. If the backdropBreakpoint came before the
  504. * current breakpoint, then the backdrop should be fading in.
  505. */
  506. const shouldShowBackdrop = backdropBreakpoint === undefined || backdropBreakpoint < currentBreakpoint;
  507. const initialBackdrop = shouldShowBackdrop ? `calc(var(--backdrop-opacity) * ${currentBreakpoint})` : '0';
  508. const backdropAnimation = createAnimation('backdropAnimation').fromTo('opacity', 0, initialBackdrop);
  509. if (shouldShowBackdrop) {
  510. backdropAnimation
  511. .beforeStyles({
  512. 'pointer-events': 'none',
  513. })
  514. .afterClearStyles(['pointer-events']);
  515. }
  516. const wrapperAnimation = createAnimation('wrapperAnimation').keyframes([
  517. { offset: 0, opacity: 1, transform: 'translateY(100%)' },
  518. { offset: 1, opacity: 1, transform: `translateY(${100 - currentBreakpoint * 100}%)` },
  519. ]);
  520. /**
  521. * This allows the content to be scrollable at any breakpoint.
  522. */
  523. const contentAnimation = !expandToScroll
  524. ? createAnimation('contentAnimation').keyframes([
  525. { offset: 0, opacity: 1, maxHeight: `${(1 - currentBreakpoint) * 100}%` },
  526. { offset: 1, opacity: 1, maxHeight: `${currentBreakpoint * 100}%` },
  527. ])
  528. : undefined;
  529. return { wrapperAnimation, backdropAnimation, contentAnimation };
  530. };
  531. const createSheetLeaveAnimation = (opts) => {
  532. const { currentBreakpoint, backdropBreakpoint } = opts;
  533. /**
  534. * Backdrop does not always fade in from 0 to 1 if backdropBreakpoint
  535. * is defined, so we need to account for that offset by figuring out
  536. * what the current backdrop value should be.
  537. */
  538. const backdropValue = `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(currentBreakpoint, backdropBreakpoint)})`;
  539. const defaultBackdrop = [
  540. { offset: 0, opacity: backdropValue },
  541. { offset: 1, opacity: 0 },
  542. ];
  543. const customBackdrop = [
  544. { offset: 0, opacity: backdropValue },
  545. { offset: backdropBreakpoint, opacity: 0 },
  546. { offset: 1, opacity: 0 },
  547. ];
  548. const backdropAnimation = createAnimation('backdropAnimation').keyframes(backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop);
  549. const wrapperAnimation = createAnimation('wrapperAnimation').keyframes([
  550. { offset: 0, opacity: 1, transform: `translateY(${100 - currentBreakpoint * 100}%)` },
  551. { offset: 1, opacity: 1, transform: `translateY(100%)` },
  552. ]);
  553. return { wrapperAnimation, backdropAnimation };
  554. };
  555. const createEnterAnimation$1 = () => {
  556. const backdropAnimation = createAnimation()
  557. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  558. .beforeStyles({
  559. 'pointer-events': 'none',
  560. })
  561. .afterClearStyles(['pointer-events']);
  562. const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
  563. return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
  564. };
  565. /**
  566. * iOS Modal Enter Animation for the Card presentation style
  567. */
  568. const iosEnterAnimation = (baseEl, opts) => {
  569. const { presentingEl, currentBreakpoint, expandToScroll } = opts;
  570. const root = getElementRoot(baseEl);
  571. const { wrapperAnimation, backdropAnimation, contentAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation$1();
  572. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  573. wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')).beforeStyles({ opacity: 1 });
  574. // The content animation is only added if scrolling is enabled for
  575. // all the breakpoints.
  576. !expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
  577. const baseAnimation = createAnimation('entering-base')
  578. .addElement(baseEl)
  579. .easing('cubic-bezier(0.32,0.72,0,1)')
  580. .duration(500)
  581. .addAnimation([wrapperAnimation])
  582. .beforeAddWrite(() => {
  583. if (expandToScroll) {
  584. // Scroll can only be done when the modal is fully expanded.
  585. return;
  586. }
  587. /**
  588. * There are some browsers that causes flickering when
  589. * dragging the content when scroll is enabled at every
  590. * breakpoint. This is due to the wrapper element being
  591. * transformed off the screen and having a snap animation.
  592. *
  593. * A workaround is to clone the footer element and append
  594. * it outside of the wrapper element. This way, the footer
  595. * is still visible and the drag can be done without
  596. * flickering. The original footer is hidden until the modal
  597. * is dismissed. This maintains the animation of the footer
  598. * when the modal is dismissed.
  599. *
  600. * The workaround needs to be done before the animation starts
  601. * so there are no flickering issues.
  602. */
  603. const ionFooter = baseEl.querySelector('ion-footer');
  604. /**
  605. * This check is needed to prevent more than one footer
  606. * from being appended to the shadow root.
  607. * Otherwise, iOS and MD enter animations would append
  608. * the footer twice.
  609. */
  610. const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
  611. if (ionFooter && !ionFooterAlreadyAppended) {
  612. const footerHeight = ionFooter.clientHeight;
  613. const clonedFooter = ionFooter.cloneNode(true);
  614. baseEl.shadowRoot.appendChild(clonedFooter);
  615. ionFooter.style.setProperty('display', 'none');
  616. ionFooter.setAttribute('aria-hidden', 'true');
  617. // Padding is added to prevent some content from being hidden.
  618. const page = baseEl.querySelector('.ion-page');
  619. page.style.setProperty('padding-bottom', `${footerHeight}px`);
  620. }
  621. });
  622. if (contentAnimation) {
  623. baseAnimation.addAnimation(contentAnimation);
  624. }
  625. if (presentingEl) {
  626. const isMobile = window.innerWidth < 768;
  627. const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
  628. const presentingElRoot = getElementRoot(presentingEl);
  629. const presentingAnimation = createAnimation().beforeStyles({
  630. transform: 'translateY(0)',
  631. 'transform-origin': 'top center',
  632. overflow: 'hidden',
  633. });
  634. const bodyEl = document.body;
  635. if (isMobile) {
  636. /**
  637. * Fallback for browsers that does not support `max()` (ex: Firefox)
  638. * No need to worry about statusbar padding since engines like Gecko
  639. * are not used as the engine for standalone Cordova/Capacitor apps
  640. */
  641. const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
  642. const modalTransform = hasCardModal ? '-10px' : transformOffset;
  643. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
  644. const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
  645. presentingAnimation
  646. .afterStyles({
  647. transform: finalTransform,
  648. })
  649. .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
  650. .addElement(presentingEl)
  651. .keyframes([
  652. { offset: 0, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' },
  653. { offset: 1, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
  654. ]);
  655. baseAnimation.addAnimation(presentingAnimation);
  656. }
  657. else {
  658. baseAnimation.addAnimation(backdropAnimation);
  659. if (!hasCardModal) {
  660. wrapperAnimation.fromTo('opacity', '0', '1');
  661. }
  662. else {
  663. const toPresentingScale = hasCardModal ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
  664. const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
  665. presentingAnimation
  666. .afterStyles({
  667. transform: finalTransform,
  668. })
  669. .addElement(presentingElRoot.querySelector('.modal-wrapper'))
  670. .keyframes([
  671. { offset: 0, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' },
  672. { offset: 1, filter: 'contrast(0.85)', transform: finalTransform },
  673. ]);
  674. const shadowAnimation = createAnimation()
  675. .afterStyles({
  676. transform: finalTransform,
  677. })
  678. .addElement(presentingElRoot.querySelector('.modal-shadow'))
  679. .keyframes([
  680. { offset: 0, opacity: '1', transform: 'translateY(0) scale(1)' },
  681. { offset: 1, opacity: '0', transform: finalTransform },
  682. ]);
  683. baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
  684. }
  685. }
  686. }
  687. else {
  688. baseAnimation.addAnimation(backdropAnimation);
  689. }
  690. return baseAnimation;
  691. };
  692. const createLeaveAnimation$1 = () => {
  693. const backdropAnimation = createAnimation().fromTo('opacity', 'var(--backdrop-opacity)', 0);
  694. const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
  695. return { backdropAnimation, wrapperAnimation };
  696. };
  697. /**
  698. * iOS Modal Leave Animation
  699. */
  700. const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
  701. const { presentingEl, currentBreakpoint, expandToScroll } = opts;
  702. const root = getElementRoot(baseEl);
  703. const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
  704. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  705. wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')).beforeStyles({ opacity: 1 });
  706. const baseAnimation = createAnimation('leaving-base')
  707. .addElement(baseEl)
  708. .easing('cubic-bezier(0.32,0.72,0,1)')
  709. .duration(duration)
  710. .addAnimation(wrapperAnimation)
  711. .beforeAddWrite(() => {
  712. if (expandToScroll) {
  713. // Scroll can only be done when the modal is fully expanded.
  714. return;
  715. }
  716. /**
  717. * If expandToScroll is disabled, we need to swap
  718. * the visibility to the original, so the footer
  719. * dismisses with the modal and doesn't stay
  720. * until the modal is removed from the DOM.
  721. */
  722. const ionFooter = baseEl.querySelector('ion-footer');
  723. if (ionFooter) {
  724. const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
  725. ionFooter.style.removeProperty('display');
  726. ionFooter.removeAttribute('aria-hidden');
  727. clonedFooter.style.setProperty('display', 'none');
  728. clonedFooter.setAttribute('aria-hidden', 'true');
  729. const page = baseEl.querySelector('.ion-page');
  730. page.style.removeProperty('padding-bottom');
  731. }
  732. });
  733. if (presentingEl) {
  734. const isMobile = window.innerWidth < 768;
  735. const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
  736. const presentingElRoot = getElementRoot(presentingEl);
  737. const presentingAnimation = createAnimation()
  738. .beforeClearStyles(['transform'])
  739. .afterClearStyles(['transform'])
  740. .onFinish((currentStep) => {
  741. // only reset background color if this is the last card-style modal
  742. if (currentStep !== 1) {
  743. return;
  744. }
  745. presentingEl.style.setProperty('overflow', '');
  746. const numModals = Array.from(bodyEl.querySelectorAll('ion-modal:not(.overlay-hidden)')).filter((m) => m.presentingElement !== undefined).length;
  747. if (numModals <= 1) {
  748. bodyEl.style.setProperty('background-color', '');
  749. }
  750. });
  751. const bodyEl = document.body;
  752. if (isMobile) {
  753. const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
  754. const modalTransform = hasCardModal ? '-10px' : transformOffset;
  755. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
  756. const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
  757. presentingAnimation.addElement(presentingEl).keyframes([
  758. { offset: 0, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
  759. { offset: 1, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' },
  760. ]);
  761. baseAnimation.addAnimation(presentingAnimation);
  762. }
  763. else {
  764. baseAnimation.addAnimation(backdropAnimation);
  765. if (!hasCardModal) {
  766. wrapperAnimation.fromTo('opacity', '1', '0');
  767. }
  768. else {
  769. const toPresentingScale = hasCardModal ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
  770. const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
  771. presentingAnimation
  772. .addElement(presentingElRoot.querySelector('.modal-wrapper'))
  773. .afterStyles({
  774. transform: 'translate3d(0, 0, 0)',
  775. })
  776. .keyframes([
  777. { offset: 0, filter: 'contrast(0.85)', transform: finalTransform },
  778. { offset: 1, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' },
  779. ]);
  780. const shadowAnimation = createAnimation()
  781. .addElement(presentingElRoot.querySelector('.modal-shadow'))
  782. .afterStyles({
  783. transform: 'translateY(0) scale(1)',
  784. })
  785. .keyframes([
  786. { offset: 0, opacity: '0', transform: finalTransform },
  787. { offset: 1, opacity: '1', transform: 'translateY(0) scale(1)' },
  788. ]);
  789. baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
  790. }
  791. }
  792. }
  793. else {
  794. baseAnimation.addAnimation(backdropAnimation);
  795. }
  796. return baseAnimation;
  797. };
  798. const createEnterAnimation = () => {
  799. const backdropAnimation = createAnimation()
  800. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  801. .beforeStyles({
  802. 'pointer-events': 'none',
  803. })
  804. .afterClearStyles(['pointer-events']);
  805. const wrapperAnimation = createAnimation().keyframes([
  806. { offset: 0, opacity: 0.01, transform: 'translateY(40px)' },
  807. { offset: 1, opacity: 1, transform: `translateY(0px)` },
  808. ]);
  809. return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
  810. };
  811. /**
  812. * Md Modal Enter Animation
  813. */
  814. const mdEnterAnimation = (baseEl, opts) => {
  815. const { currentBreakpoint, expandToScroll } = opts;
  816. const root = getElementRoot(baseEl);
  817. const { wrapperAnimation, backdropAnimation, contentAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
  818. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  819. wrapperAnimation.addElement(root.querySelector('.modal-wrapper'));
  820. // The content animation is only added if scrolling is enabled for
  821. // all the breakpoints.
  822. expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
  823. const baseAnimation = createAnimation()
  824. .addElement(baseEl)
  825. .easing('cubic-bezier(0.36,0.66,0.04,1)')
  826. .duration(280)
  827. .addAnimation([backdropAnimation, wrapperAnimation])
  828. .beforeAddWrite(() => {
  829. if (expandToScroll) {
  830. // Scroll can only be done when the modal is fully expanded.
  831. return;
  832. }
  833. /**
  834. * There are some browsers that causes flickering when
  835. * dragging the content when scroll is enabled at every
  836. * breakpoint. This is due to the wrapper element being
  837. * transformed off the screen and having a snap animation.
  838. *
  839. * A workaround is to clone the footer element and append
  840. * it outside of the wrapper element. This way, the footer
  841. * is still visible and the drag can be done without
  842. * flickering. The original footer is hidden until the modal
  843. * is dismissed. This maintains the animation of the footer
  844. * when the modal is dismissed.
  845. *
  846. * The workaround needs to be done before the animation starts
  847. * so there are no flickering issues.
  848. */
  849. const ionFooter = baseEl.querySelector('ion-footer');
  850. /**
  851. * This check is needed to prevent more than one footer
  852. * from being appended to the shadow root.
  853. * Otherwise, iOS and MD enter animations would append
  854. * the footer twice.
  855. */
  856. const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
  857. if (ionFooter && !ionFooterAlreadyAppended) {
  858. const footerHeight = ionFooter.clientHeight;
  859. const clonedFooter = ionFooter.cloneNode(true);
  860. baseEl.shadowRoot.appendChild(clonedFooter);
  861. ionFooter.style.setProperty('display', 'none');
  862. ionFooter.setAttribute('aria-hidden', 'true');
  863. // Padding is added to prevent some content from being hidden.
  864. const page = baseEl.querySelector('.ion-page');
  865. page.style.setProperty('padding-bottom', `${footerHeight}px`);
  866. }
  867. });
  868. if (contentAnimation) {
  869. baseAnimation.addAnimation(contentAnimation);
  870. }
  871. return baseAnimation;
  872. };
  873. const createLeaveAnimation = () => {
  874. const backdropAnimation = createAnimation().fromTo('opacity', 'var(--backdrop-opacity)', 0);
  875. const wrapperAnimation = createAnimation().keyframes([
  876. { offset: 0, opacity: 0.99, transform: `translateY(0px)` },
  877. { offset: 1, opacity: 0, transform: 'translateY(40px)' },
  878. ]);
  879. return { backdropAnimation, wrapperAnimation };
  880. };
  881. /**
  882. * Md Modal Leave Animation
  883. */
  884. const mdLeaveAnimation = (baseEl, opts) => {
  885. const { currentBreakpoint, expandToScroll } = opts;
  886. const root = getElementRoot(baseEl);
  887. const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
  888. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  889. wrapperAnimation.addElement(root.querySelector('.modal-wrapper'));
  890. const baseAnimation = createAnimation()
  891. .easing('cubic-bezier(0.47,0,0.745,0.715)')
  892. .duration(200)
  893. .addAnimation([backdropAnimation, wrapperAnimation])
  894. .beforeAddWrite(() => {
  895. if (expandToScroll) {
  896. // Scroll can only be done when the modal is fully expanded.
  897. return;
  898. }
  899. /**
  900. * If expandToScroll is disabled, we need to swap
  901. * the visibility to the original, so the footer
  902. * dismisses with the modal and doesn't stay
  903. * until the modal is removed from the DOM.
  904. */
  905. const ionFooter = baseEl.querySelector('ion-footer');
  906. if (ionFooter) {
  907. const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
  908. ionFooter.style.removeProperty('display');
  909. ionFooter.removeAttribute('aria-hidden');
  910. clonedFooter.style.setProperty('display', 'none');
  911. clonedFooter.setAttribute('aria-hidden', 'true');
  912. const page = baseEl.querySelector('.ion-page');
  913. page.style.removeProperty('padding-bottom');
  914. }
  915. });
  916. return baseAnimation;
  917. };
  918. const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
  919. // Defaults for the sheet swipe animation
  920. const defaultBackdrop = [
  921. { offset: 0, opacity: 'var(--backdrop-opacity)' },
  922. { offset: 1, opacity: 0.01 },
  923. ];
  924. const customBackdrop = [
  925. { offset: 0, opacity: 'var(--backdrop-opacity)' },
  926. { offset: 1 - backdropBreakpoint, opacity: 0 },
  927. { offset: 1, opacity: 0 },
  928. ];
  929. const SheetDefaults = {
  930. WRAPPER_KEYFRAMES: [
  931. { offset: 0, transform: 'translateY(0%)' },
  932. { offset: 1, transform: 'translateY(100%)' },
  933. ],
  934. BACKDROP_KEYFRAMES: backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop,
  935. CONTENT_KEYFRAMES: [
  936. { offset: 0, maxHeight: '100%' },
  937. { offset: 1, maxHeight: '0%' },
  938. ],
  939. };
  940. const contentEl = baseEl.querySelector('ion-content');
  941. const height = wrapperEl.clientHeight;
  942. let currentBreakpoint = initialBreakpoint;
  943. let offset = 0;
  944. let canDismissBlocksGesture = false;
  945. let cachedScrollEl = null;
  946. const canDismissMaxStep = 0.95;
  947. const maxBreakpoint = breakpoints[breakpoints.length - 1];
  948. const minBreakpoint = breakpoints[0];
  949. const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
  950. const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
  951. const contentAnimation = animation.childAnimations.find((ani) => ani.id === 'contentAnimation');
  952. const enableBackdrop = () => {
  953. baseEl.style.setProperty('pointer-events', 'auto');
  954. backdropEl.style.setProperty('pointer-events', 'auto');
  955. /**
  956. * When the backdrop is enabled, elements such
  957. * as inputs should not be focusable outside
  958. * the sheet.
  959. */
  960. baseEl.classList.remove(FOCUS_TRAP_DISABLE_CLASS);
  961. };
  962. const disableBackdrop = () => {
  963. baseEl.style.setProperty('pointer-events', 'none');
  964. backdropEl.style.setProperty('pointer-events', 'none');
  965. /**
  966. * When the backdrop is enabled, elements such
  967. * as inputs should not be focusable outside
  968. * the sheet.
  969. * Adding this class disables focus trapping
  970. * for the sheet temporarily.
  971. */
  972. baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
  973. };
  974. /**
  975. * Toggles the visible modal footer when `expandToScroll` is disabled.
  976. * @param footer The footer to show.
  977. */
  978. const swapFooterVisibility = (footer) => {
  979. const originalFooter = baseEl.querySelector('ion-footer');
  980. if (!originalFooter) {
  981. return;
  982. }
  983. const clonedFooter = wrapperEl.nextElementSibling;
  984. const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
  985. const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
  986. footerToShow.style.removeProperty('display');
  987. footerToShow.removeAttribute('aria-hidden');
  988. const page = baseEl.querySelector('.ion-page');
  989. if (footer === 'original') {
  990. page.style.removeProperty('padding-bottom');
  991. }
  992. else {
  993. const pagePadding = footerToShow.clientHeight;
  994. page.style.setProperty('padding-bottom', `${pagePadding}px`);
  995. }
  996. footerToHide.style.setProperty('display', 'none');
  997. footerToHide.setAttribute('aria-hidden', 'true');
  998. };
  999. /**
  1000. * After the entering animation completes,
  1001. * we need to set the animation to go from
  1002. * offset 0 to offset 1 so that users can
  1003. * swipe in any direction. We then set the
  1004. * animation offset to the current breakpoint
  1005. * so there is no flickering.
  1006. */
  1007. if (wrapperAnimation && backdropAnimation) {
  1008. wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
  1009. backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
  1010. contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
  1011. animation.progressStart(true, 1 - currentBreakpoint);
  1012. /**
  1013. * If backdrop is not enabled, then content
  1014. * behind modal should be clickable. To do this, we need
  1015. * to remove pointer-events from ion-modal as a whole.
  1016. * ion-backdrop and .modal-wrapper always have pointer-events: auto
  1017. * applied, so the modal content can still be interacted with.
  1018. */
  1019. const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
  1020. if (shouldEnableBackdrop) {
  1021. enableBackdrop();
  1022. }
  1023. else {
  1024. disableBackdrop();
  1025. }
  1026. }
  1027. if (contentEl && currentBreakpoint !== maxBreakpoint && expandToScroll) {
  1028. contentEl.scrollY = false;
  1029. }
  1030. const canStart = (detail) => {
  1031. /**
  1032. * If we are swiping on the content, swiping should only be possible if the content
  1033. * is scrolled all the way to the top so that we do not interfere with scrolling.
  1034. *
  1035. * We cannot assume that the `ion-content` target will remain consistent between swipes.
  1036. * For example, when using ion-nav within a modal it is possible to swipe, push a view,
  1037. * and then swipe again. The target content will not be the same between swipes.
  1038. */
  1039. const contentEl = findClosestIonContent(detail.event.target);
  1040. currentBreakpoint = getCurrentBreakpoint();
  1041. /**
  1042. * If `expandToScroll` is disabled, we should not allow the swipe gesture
  1043. * to start if the content is not scrolled to the top.
  1044. */
  1045. if (!expandToScroll && contentEl) {
  1046. const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
  1047. return scrollEl.scrollTop === 0;
  1048. }
  1049. if (currentBreakpoint === 1 && contentEl) {
  1050. /**
  1051. * The modal should never swipe to close on the content with a refresher.
  1052. * Note 1: We cannot solve this by making this gesture have a higher priority than
  1053. * the refresher gesture as the iOS native refresh gesture uses a scroll listener in
  1054. * addition to a gesture.
  1055. *
  1056. * Note 2: Do not use getScrollElement here because we need this to be a synchronous
  1057. * operation, and getScrollElement is asynchronous.
  1058. */
  1059. const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
  1060. const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
  1061. return !hasRefresherInContent && scrollEl.scrollTop === 0;
  1062. }
  1063. return true;
  1064. };
  1065. const onStart = (detail) => {
  1066. /**
  1067. * If canDismiss is anything other than `true`
  1068. * then users should be able to swipe down
  1069. * until a threshold is hit. At that point,
  1070. * the card modal should not proceed any further.
  1071. *
  1072. * canDismiss is never fired via gesture if there is
  1073. * no 0 breakpoint. However, it can be fired if the user
  1074. * presses Esc or the hardware back button.
  1075. * TODO (FW-937)
  1076. * Remove undefined check
  1077. */
  1078. canDismissBlocksGesture = baseEl.canDismiss !== undefined && baseEl.canDismiss !== true && minBreakpoint === 0;
  1079. /**
  1080. * Cache the scroll element reference when the gesture starts,
  1081. * this allows us to avoid querying the DOM for the target in onMove,
  1082. * which would impact performance significantly.
  1083. */
  1084. if (!expandToScroll) {
  1085. const targetEl = findClosestIonContent(detail.event.target);
  1086. cachedScrollEl =
  1087. targetEl && isIonContent(targetEl) ? getElementRoot(targetEl).querySelector('.inner-scroll') : targetEl;
  1088. }
  1089. /**
  1090. * If expandToScroll is disabled, we need to swap
  1091. * the footer visibility to the original, so if the modal
  1092. * is dismissed, the footer dismisses with the modal
  1093. * and doesn't stay on the screen after the modal is gone.
  1094. */
  1095. if (!expandToScroll) {
  1096. swapFooterVisibility('original');
  1097. }
  1098. /**
  1099. * If we are pulling down, then it is possible we are pulling on the content.
  1100. * We do not want scrolling to happen at the same time as the gesture.
  1101. */
  1102. if (detail.deltaY > 0 && contentEl) {
  1103. contentEl.scrollY = false;
  1104. }
  1105. raf(() => {
  1106. /**
  1107. * Dismisses the open keyboard when the sheet drag gesture is started.
  1108. * Sets the focus onto the modal element.
  1109. */
  1110. baseEl.focus();
  1111. });
  1112. animation.progressStart(true, 1 - currentBreakpoint);
  1113. };
  1114. const onMove = (detail) => {
  1115. /**
  1116. * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
  1117. * the scrollable content, we should not allow the swipe gesture to continue.
  1118. */
  1119. if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl) {
  1120. return;
  1121. }
  1122. /**
  1123. * If we are pulling down, then it is possible we are pulling on the content.
  1124. * We do not want scrolling to happen at the same time as the gesture.
  1125. * This accounts for when the user scrolls down, scrolls all the way up, and then
  1126. * pulls down again such that the modal should start to move.
  1127. */
  1128. if (detail.deltaY > 0 && contentEl) {
  1129. contentEl.scrollY = false;
  1130. }
  1131. /**
  1132. * Given the change in gesture position on the Y axis,
  1133. * compute where the offset of the animation should be
  1134. * relative to where the user dragged.
  1135. */
  1136. const initialStep = 1 - currentBreakpoint;
  1137. const secondToLastBreakpoint = breakpoints.length > 1 ? 1 - breakpoints[1] : undefined;
  1138. const step = initialStep + detail.deltaY / height;
  1139. const isAttemptingDismissWithCanDismiss = secondToLastBreakpoint !== undefined && step >= secondToLastBreakpoint && canDismissBlocksGesture;
  1140. /**
  1141. * If we are blocking the gesture from dismissing,
  1142. * set the max step value so that the sheet cannot be
  1143. * completely hidden.
  1144. */
  1145. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  1146. /**
  1147. * If we are blocking the gesture from
  1148. * dismissing, calculate the spring modifier value
  1149. * this will be added to the starting breakpoint
  1150. * value to give the gesture a spring-like feeling.
  1151. * Note that when isAttemptingDismissWithCanDismiss is true,
  1152. * the modifier is always added to the breakpoint that
  1153. * appears right after the 0 breakpoint.
  1154. *
  1155. * Note that this modifier is essentially the progression
  1156. * between secondToLastBreakpoint and maxStep which is
  1157. * why we subtract secondToLastBreakpoint. This lets us get
  1158. * the result as a value from 0 to 1.
  1159. */
  1160. const processedStep = isAttemptingDismissWithCanDismiss && secondToLastBreakpoint !== undefined
  1161. ? secondToLastBreakpoint +
  1162. calculateSpringStep((step - secondToLastBreakpoint) / (maxStep - secondToLastBreakpoint))
  1163. : step;
  1164. offset = clamp(0.0001, processedStep, maxStep);
  1165. animation.progressStep(offset);
  1166. };
  1167. const onEnd = (detail) => {
  1168. /**
  1169. * If expandToScroll is disabled, we should not allow the moveSheetToBreakpoint
  1170. * function to be called if the user is trying to swipe content upwards and the content
  1171. * is not scrolled to the top.
  1172. */
  1173. if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
  1174. return;
  1175. }
  1176. /**
  1177. * When the gesture releases, we need to determine
  1178. * the closest breakpoint to snap to.
  1179. */
  1180. const velocity = detail.velocityY;
  1181. const threshold = (detail.deltaY + velocity * 350) / height;
  1182. const diff = currentBreakpoint - threshold;
  1183. const closest = breakpoints.reduce((a, b) => {
  1184. return Math.abs(b - diff) < Math.abs(a - diff) ? b : a;
  1185. });
  1186. moveSheetToBreakpoint({
  1187. breakpoint: closest,
  1188. breakpointOffset: offset,
  1189. canDismiss: canDismissBlocksGesture,
  1190. /**
  1191. * The swipe is user-driven, so we should
  1192. * always animate when the gesture ends.
  1193. */
  1194. animated: true,
  1195. });
  1196. };
  1197. const moveSheetToBreakpoint = (options) => {
  1198. const { breakpoint, canDismiss, breakpointOffset, animated } = options;
  1199. /**
  1200. * canDismiss should only prevent snapping
  1201. * when users are trying to dismiss. If canDismiss
  1202. * is present but the user is trying to swipe upwards,
  1203. * we should allow that to happen,
  1204. */
  1205. const shouldPreventDismiss = canDismiss && breakpoint === 0;
  1206. const snapToBreakpoint = shouldPreventDismiss ? currentBreakpoint : breakpoint;
  1207. const shouldRemainOpen = snapToBreakpoint !== 0;
  1208. currentBreakpoint = 0;
  1209. /**
  1210. * Update the animation so that it plays from
  1211. * the last offset to the closest snap point.
  1212. */
  1213. if (wrapperAnimation && backdropAnimation) {
  1214. wrapperAnimation.keyframes([
  1215. { offset: 0, transform: `translateY(${breakpointOffset * 100}%)` },
  1216. { offset: 1, transform: `translateY(${(1 - snapToBreakpoint) * 100}%)` },
  1217. ]);
  1218. backdropAnimation.keyframes([
  1219. {
  1220. offset: 0,
  1221. opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(1 - breakpointOffset, backdropBreakpoint)})`,
  1222. },
  1223. {
  1224. offset: 1,
  1225. opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(snapToBreakpoint, backdropBreakpoint)})`,
  1226. },
  1227. ]);
  1228. if (contentAnimation) {
  1229. /**
  1230. * The modal content should scroll at any breakpoint when expandToScroll
  1231. * is disabled. In order to do this, the content needs to be completely
  1232. * viewable so scrolling can access everything. Otherwise, the default
  1233. * behavior would show the content off the screen and only allow
  1234. * scrolling when the sheet is fully expanded.
  1235. */
  1236. contentAnimation.keyframes([
  1237. { offset: 0, maxHeight: `${(1 - breakpointOffset) * 100}%` },
  1238. { offset: 1, maxHeight: `${snapToBreakpoint * 100}%` },
  1239. ]);
  1240. }
  1241. animation.progressStep(0);
  1242. }
  1243. /**
  1244. * Gesture should remain disabled until the
  1245. * snapping animation completes.
  1246. */
  1247. gesture.enable(false);
  1248. /**
  1249. * If expandToScroll is disabled, we need to swap
  1250. * the footer visibility to the cloned one so the footer
  1251. * doesn't flicker when the sheet's height is animated.
  1252. */
  1253. if (!expandToScroll && shouldRemainOpen) {
  1254. swapFooterVisibility('cloned');
  1255. }
  1256. if (shouldPreventDismiss) {
  1257. handleCanDismiss(baseEl, animation);
  1258. }
  1259. else if (!shouldRemainOpen) {
  1260. onDismiss();
  1261. }
  1262. /**
  1263. * Enables scrolling immediately if the sheet is about to fully expand
  1264. * or if it allows scrolling at any breakpoint. Without this, there would
  1265. * be a ~500ms delay while the modal animation completes, causing a
  1266. * noticeable lag. Native iOS allows scrolling as soon as the gesture is
  1267. * released, so we align with that behavior.
  1268. */
  1269. if (contentEl && (snapToBreakpoint === breakpoints[breakpoints.length - 1] || !expandToScroll)) {
  1270. contentEl.scrollY = true;
  1271. }
  1272. return new Promise((resolve) => {
  1273. animation
  1274. .onFinish(() => {
  1275. if (shouldRemainOpen) {
  1276. /**
  1277. * Once the snapping animation completes,
  1278. * we need to reset the animation to go
  1279. * from 0 to 1 so users can swipe in any direction.
  1280. * We then set the animation offset to the current
  1281. * breakpoint so that it starts at the snapped position.
  1282. */
  1283. if (wrapperAnimation && backdropAnimation) {
  1284. raf(() => {
  1285. wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
  1286. backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
  1287. contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
  1288. animation.progressStart(true, 1 - snapToBreakpoint);
  1289. currentBreakpoint = snapToBreakpoint;
  1290. onBreakpointChange(currentBreakpoint);
  1291. /**
  1292. * Backdrop should become enabled
  1293. * after the backdropBreakpoint value
  1294. */
  1295. const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
  1296. if (shouldEnableBackdrop) {
  1297. enableBackdrop();
  1298. }
  1299. else {
  1300. disableBackdrop();
  1301. }
  1302. gesture.enable(true);
  1303. resolve();
  1304. });
  1305. }
  1306. else {
  1307. gesture.enable(true);
  1308. resolve();
  1309. }
  1310. }
  1311. else {
  1312. resolve();
  1313. }
  1314. /**
  1315. * This must be a one time callback
  1316. * otherwise a new callback will
  1317. * be added every time onEnd runs.
  1318. */
  1319. }, { oneTimeCallback: true })
  1320. .progressEnd(1, 0, animated ? 500 : 0);
  1321. });
  1322. };
  1323. const gesture = createGesture({
  1324. el: wrapperEl,
  1325. gestureName: 'modalSheet',
  1326. gesturePriority: 40,
  1327. direction: 'y',
  1328. threshold: 10,
  1329. canStart,
  1330. onStart,
  1331. onMove,
  1332. onEnd,
  1333. });
  1334. return {
  1335. gesture,
  1336. moveSheetToBreakpoint,
  1337. };
  1338. };
  1339. const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer ion-toolbar:first-of-type{padding-top:6px}";
  1340. const IonModalIosStyle0 = modalIosCss;
  1341. const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
  1342. const IonModalMdStyle0 = modalMdCss;
  1343. const Modal = class {
  1344. constructor(hostRef) {
  1345. registerInstance(this, hostRef);
  1346. this.didPresent = createEvent(this, "ionModalDidPresent", 7);
  1347. this.willPresent = createEvent(this, "ionModalWillPresent", 7);
  1348. this.willDismiss = createEvent(this, "ionModalWillDismiss", 7);
  1349. this.didDismiss = createEvent(this, "ionModalDidDismiss", 7);
  1350. this.ionBreakpointDidChange = createEvent(this, "ionBreakpointDidChange", 7);
  1351. this.didPresentShorthand = createEvent(this, "didPresent", 7);
  1352. this.willPresentShorthand = createEvent(this, "willPresent", 7);
  1353. this.willDismissShorthand = createEvent(this, "willDismiss", 7);
  1354. this.didDismissShorthand = createEvent(this, "didDismiss", 7);
  1355. this.ionMount = createEvent(this, "ionMount", 7);
  1356. this.lockController = createLockController();
  1357. this.triggerController = createTriggerController();
  1358. this.coreDelegate = CoreDelegate();
  1359. this.isSheetModal = false;
  1360. this.inheritedAttributes = {};
  1361. this.inline = false;
  1362. // Whether or not modal is being dismissed via gesture
  1363. this.gestureAnimationDismissing = false;
  1364. this.onHandleClick = () => {
  1365. const { sheetTransition, handleBehavior } = this;
  1366. if (handleBehavior !== 'cycle' || sheetTransition !== undefined) {
  1367. /**
  1368. * The sheet modal should not advance to the next breakpoint
  1369. * if the handle behavior is not `cycle` or if the handle
  1370. * is clicked while the sheet is moving to a breakpoint.
  1371. */
  1372. return;
  1373. }
  1374. this.moveToNextBreakpoint();
  1375. };
  1376. this.onBackdropTap = () => {
  1377. const { sheetTransition } = this;
  1378. if (sheetTransition !== undefined) {
  1379. /**
  1380. * When the handle is double clicked at the largest breakpoint,
  1381. * it will start to move to the first breakpoint. While transitioning,
  1382. * the backdrop will often receive the second click. We prevent the
  1383. * backdrop from dismissing the modal while moving between breakpoints.
  1384. */
  1385. return;
  1386. }
  1387. this.dismiss(undefined, BACKDROP);
  1388. };
  1389. this.onLifecycle = (modalEvent) => {
  1390. const el = this.usersElement;
  1391. const name = LIFECYCLE_MAP[modalEvent.type];
  1392. if (el && name) {
  1393. const ev = new CustomEvent(name, {
  1394. bubbles: false,
  1395. cancelable: false,
  1396. detail: modalEvent.detail,
  1397. });
  1398. el.dispatchEvent(ev);
  1399. }
  1400. };
  1401. this.presented = false;
  1402. this.hasController = false;
  1403. this.overlayIndex = undefined;
  1404. this.delegate = undefined;
  1405. this.keyboardClose = true;
  1406. this.enterAnimation = undefined;
  1407. this.leaveAnimation = undefined;
  1408. this.breakpoints = undefined;
  1409. this.expandToScroll = true;
  1410. this.initialBreakpoint = undefined;
  1411. this.backdropBreakpoint = 0;
  1412. this.handle = undefined;
  1413. this.handleBehavior = 'none';
  1414. this.component = undefined;
  1415. this.componentProps = undefined;
  1416. this.cssClass = undefined;
  1417. this.backdropDismiss = true;
  1418. this.showBackdrop = true;
  1419. this.animated = true;
  1420. this.presentingElement = undefined;
  1421. this.htmlAttributes = undefined;
  1422. this.isOpen = false;
  1423. this.trigger = undefined;
  1424. this.keepContentsMounted = false;
  1425. this.focusTrap = true;
  1426. this.canDismiss = true;
  1427. }
  1428. onIsOpenChange(newValue, oldValue) {
  1429. if (newValue === true && oldValue === false) {
  1430. this.present();
  1431. }
  1432. else if (newValue === false && oldValue === true) {
  1433. this.dismiss();
  1434. }
  1435. }
  1436. triggerChanged() {
  1437. const { trigger, el, triggerController } = this;
  1438. if (trigger) {
  1439. triggerController.addClickListener(el, trigger);
  1440. }
  1441. }
  1442. breakpointsChanged(breakpoints) {
  1443. if (breakpoints !== undefined) {
  1444. this.sortedBreakpoints = breakpoints.sort((a, b) => a - b);
  1445. }
  1446. }
  1447. connectedCallback() {
  1448. const { el } = this;
  1449. prepareOverlay(el);
  1450. this.triggerChanged();
  1451. }
  1452. disconnectedCallback() {
  1453. this.triggerController.removeClickListener();
  1454. }
  1455. componentWillLoad() {
  1456. var _a;
  1457. const { breakpoints, initialBreakpoint, el, htmlAttributes } = this;
  1458. const isSheetModal = (this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined);
  1459. const attributesToInherit = ['aria-label', 'role'];
  1460. this.inheritedAttributes = inheritAttributes(el, attributesToInherit);
  1461. /**
  1462. * When using a controller modal you can set attributes
  1463. * using the htmlAttributes property. Since the above attributes
  1464. * need to be inherited inside of the modal, we need to look
  1465. * and see if these attributes are being set via htmlAttributes.
  1466. *
  1467. * We could alternatively move this to componentDidLoad to simplify the work
  1468. * here, but we'd then need to make inheritedAttributes a State variable,
  1469. * thus causing another render to always happen after the first render.
  1470. */
  1471. if (htmlAttributes !== undefined) {
  1472. attributesToInherit.forEach((attribute) => {
  1473. const attributeValue = htmlAttributes[attribute];
  1474. if (attributeValue) {
  1475. /**
  1476. * If an attribute we need to inherit was
  1477. * set using htmlAttributes then add it to
  1478. * inheritedAttributes and remove it from htmlAttributes.
  1479. * This ensures the attribute is inherited and not
  1480. * set on the host.
  1481. *
  1482. * In this case, if an inherited attribute is set
  1483. * on the host element and using htmlAttributes then
  1484. * htmlAttributes wins, but that's not a pattern that we recommend.
  1485. * The only time you'd need htmlAttributes is when using modalController.
  1486. */
  1487. this.inheritedAttributes = Object.assign(Object.assign({}, this.inheritedAttributes), { [attribute]: htmlAttributes[attribute] });
  1488. delete htmlAttributes[attribute];
  1489. }
  1490. });
  1491. }
  1492. if (isSheetModal) {
  1493. this.currentBreakpoint = this.initialBreakpoint;
  1494. }
  1495. if (breakpoints !== undefined && initialBreakpoint !== undefined && !breakpoints.includes(initialBreakpoint)) {
  1496. printIonWarning('[ion-modal] - Your breakpoints array must include the initialBreakpoint value.');
  1497. }
  1498. if (!((_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id)) {
  1499. setOverlayId(this.el);
  1500. }
  1501. }
  1502. componentDidLoad() {
  1503. /**
  1504. * If modal was rendered with isOpen="true"
  1505. * then we should open modal immediately.
  1506. */
  1507. if (this.isOpen === true) {
  1508. raf(() => this.present());
  1509. }
  1510. this.breakpointsChanged(this.breakpoints);
  1511. /**
  1512. * When binding values in frameworks such as Angular
  1513. * it is possible for the value to be set after the Web Component
  1514. * initializes but before the value watcher is set up in Stencil.
  1515. * As a result, the watcher callback may not be fired.
  1516. * We work around this by manually calling the watcher
  1517. * callback when the component has loaded and the watcher
  1518. * is configured.
  1519. */
  1520. this.triggerChanged();
  1521. }
  1522. /**
  1523. * Determines whether or not an overlay
  1524. * is being used inline or via a controller/JS
  1525. * and returns the correct delegate.
  1526. * By default, subsequent calls to getDelegate
  1527. * will use a cached version of the delegate.
  1528. * This is useful for calling dismiss after
  1529. * present so that the correct delegate is given.
  1530. */
  1531. getDelegate(force = false) {
  1532. if (this.workingDelegate && !force) {
  1533. return {
  1534. delegate: this.workingDelegate,
  1535. inline: this.inline,
  1536. };
  1537. }
  1538. /**
  1539. * If using overlay inline
  1540. * we potentially need to use the coreDelegate
  1541. * so that this works in vanilla JS apps.
  1542. * If a developer has presented this component
  1543. * via a controller, then we can assume
  1544. * the component is already in the
  1545. * correct place.
  1546. */
  1547. const parentEl = this.el.parentNode;
  1548. const inline = (this.inline = parentEl !== null && !this.hasController);
  1549. const delegate = (this.workingDelegate = inline ? this.delegate || this.coreDelegate : this.delegate);
  1550. return { inline, delegate };
  1551. }
  1552. /**
  1553. * Determines whether or not the
  1554. * modal is allowed to dismiss based
  1555. * on the state of the canDismiss prop.
  1556. */
  1557. async checkCanDismiss(data, role) {
  1558. const { canDismiss } = this;
  1559. if (typeof canDismiss === 'function') {
  1560. return canDismiss(data, role);
  1561. }
  1562. return canDismiss;
  1563. }
  1564. /**
  1565. * Present the modal overlay after it has been created.
  1566. */
  1567. async present() {
  1568. const unlock = await this.lockController.lock();
  1569. if (this.presented) {
  1570. unlock();
  1571. return;
  1572. }
  1573. const { presentingElement, el } = this;
  1574. /**
  1575. * If the modal is presented multiple times (inline modals), we
  1576. * need to reset the current breakpoint to the initial breakpoint.
  1577. */
  1578. this.currentBreakpoint = this.initialBreakpoint;
  1579. const { inline, delegate } = this.getDelegate(true);
  1580. /**
  1581. * Emit ionMount so JS Frameworks have an opportunity
  1582. * to add the child component to the DOM. The child
  1583. * component will be assigned to this.usersElement below.
  1584. */
  1585. this.ionMount.emit();
  1586. this.usersElement = await attachComponent(delegate, el, this.component, ['ion-page'], this.componentProps, inline);
  1587. /**
  1588. * When using the lazy loaded build of Stencil, we need to wait
  1589. * for every Stencil component instance to be ready before presenting
  1590. * otherwise there can be a flash of unstyled content. With the
  1591. * custom elements bundle we need to wait for the JS framework
  1592. * mount the inner contents of the overlay otherwise WebKit may
  1593. * get the transition incorrect.
  1594. */
  1595. if (hasLazyBuild(el)) {
  1596. await deepReady(this.usersElement);
  1597. /**
  1598. * If keepContentsMounted="true" then the
  1599. * JS Framework has already mounted the inner
  1600. * contents so there is no need to wait.
  1601. * Otherwise, we need to wait for the JS
  1602. * Framework to mount the inner contents
  1603. * of this component.
  1604. */
  1605. }
  1606. else if (!this.keepContentsMounted) {
  1607. await waitForMount();
  1608. }
  1609. writeTask(() => this.el.classList.add('show-modal'));
  1610. const hasCardModal = presentingElement !== undefined;
  1611. /**
  1612. * We need to change the status bar at the
  1613. * start of the animation so that it completes
  1614. * by the time the card animation is done.
  1615. */
  1616. if (hasCardModal && getIonMode(this) === 'ios') {
  1617. // Cache the original status bar color before the modal is presented
  1618. this.statusBarStyle = await StatusBar.getStyle();
  1619. setCardStatusBarDark();
  1620. }
  1621. await present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, {
  1622. presentingEl: presentingElement,
  1623. currentBreakpoint: this.initialBreakpoint,
  1624. backdropBreakpoint: this.backdropBreakpoint,
  1625. expandToScroll: this.expandToScroll,
  1626. });
  1627. /* tslint:disable-next-line */
  1628. if (typeof window !== 'undefined') {
  1629. /**
  1630. * This needs to be setup before any
  1631. * non-transition async work so it can be dereferenced
  1632. * in the dismiss method. The dismiss method
  1633. * only waits for the entering transition
  1634. * to finish. It does not wait for all of the `present`
  1635. * method to resolve.
  1636. */
  1637. this.keyboardOpenCallback = () => {
  1638. if (this.gesture) {
  1639. /**
  1640. * When the native keyboard is opened and the webview
  1641. * is resized, the gesture implementation will become unresponsive
  1642. * and enter a free-scroll mode.
  1643. *
  1644. * When the keyboard is opened, we disable the gesture for
  1645. * a single frame and re-enable once the contents have repositioned
  1646. * from the keyboard placement.
  1647. */
  1648. this.gesture.enable(false);
  1649. raf(() => {
  1650. if (this.gesture) {
  1651. this.gesture.enable(true);
  1652. }
  1653. });
  1654. }
  1655. };
  1656. window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
  1657. }
  1658. if (this.isSheetModal) {
  1659. this.initSheetGesture();
  1660. }
  1661. else if (hasCardModal) {
  1662. this.initSwipeToClose();
  1663. }
  1664. unlock();
  1665. }
  1666. initSwipeToClose() {
  1667. var _a;
  1668. if (getIonMode(this) !== 'ios') {
  1669. return;
  1670. }
  1671. const { el } = this;
  1672. // All of the elements needed for the swipe gesture
  1673. // should be in the DOM and referenced by now, except
  1674. // for the presenting el
  1675. const animationBuilder = this.leaveAnimation || config.get('modalLeave', iosLeaveAnimation);
  1676. const ani = (this.animation = animationBuilder(el, {
  1677. presentingEl: this.presentingElement,
  1678. expandToScroll: this.expandToScroll,
  1679. }));
  1680. const contentEl = findIonContent(el);
  1681. if (!contentEl) {
  1682. printIonContentErrorMsg(el);
  1683. return;
  1684. }
  1685. const statusBarStyle = (_a = this.statusBarStyle) !== null && _a !== void 0 ? _a : Style.Default;
  1686. this.gesture = createSwipeToCloseGesture(el, ani, statusBarStyle, () => {
  1687. /**
  1688. * While the gesture animation is finishing
  1689. * it is possible for a user to tap the backdrop.
  1690. * This would result in the dismiss animation
  1691. * being played again. Typically this is avoided
  1692. * by setting `presented = false` on the overlay
  1693. * component; however, we cannot do that here as
  1694. * that would prevent the element from being
  1695. * removed from the DOM.
  1696. */
  1697. this.gestureAnimationDismissing = true;
  1698. /**
  1699. * Reset the status bar style as the dismiss animation
  1700. * starts otherwise the status bar will be the wrong
  1701. * color for the duration of the dismiss animation.
  1702. * The dismiss method does this as well, but
  1703. * in this case it's only called once the animation
  1704. * has finished.
  1705. */
  1706. setCardStatusBarDefault(this.statusBarStyle);
  1707. this.animation.onFinish(async () => {
  1708. await this.dismiss(undefined, GESTURE);
  1709. this.gestureAnimationDismissing = false;
  1710. });
  1711. });
  1712. this.gesture.enable(true);
  1713. }
  1714. initSheetGesture() {
  1715. const { wrapperEl, initialBreakpoint, backdropBreakpoint } = this;
  1716. if (!wrapperEl || initialBreakpoint === undefined) {
  1717. return;
  1718. }
  1719. const animationBuilder = this.enterAnimation || config.get('modalEnter', iosEnterAnimation);
  1720. const ani = (this.animation = animationBuilder(this.el, {
  1721. presentingEl: this.presentingElement,
  1722. currentBreakpoint: initialBreakpoint,
  1723. backdropBreakpoint,
  1724. expandToScroll: this.expandToScroll,
  1725. }));
  1726. ani.progressStart(true, 1);
  1727. const { gesture, moveSheetToBreakpoint } = createSheetGesture(this.el, this.backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, ani, this.sortedBreakpoints, this.expandToScroll, () => { var _a; return (_a = this.currentBreakpoint) !== null && _a !== void 0 ? _a : 0; }, () => this.sheetOnDismiss(), (breakpoint) => {
  1728. if (this.currentBreakpoint !== breakpoint) {
  1729. this.currentBreakpoint = breakpoint;
  1730. this.ionBreakpointDidChange.emit({ breakpoint });
  1731. }
  1732. });
  1733. this.gesture = gesture;
  1734. this.moveSheetToBreakpoint = moveSheetToBreakpoint;
  1735. this.gesture.enable(true);
  1736. }
  1737. sheetOnDismiss() {
  1738. /**
  1739. * While the gesture animation is finishing
  1740. * it is possible for a user to tap the backdrop.
  1741. * This would result in the dismiss animation
  1742. * being played again. Typically this is avoided
  1743. * by setting `presented = false` on the overlay
  1744. * component; however, we cannot do that here as
  1745. * that would prevent the element from being
  1746. * removed from the DOM.
  1747. */
  1748. this.gestureAnimationDismissing = true;
  1749. this.animation.onFinish(async () => {
  1750. this.currentBreakpoint = 0;
  1751. this.ionBreakpointDidChange.emit({ breakpoint: this.currentBreakpoint });
  1752. await this.dismiss(undefined, GESTURE);
  1753. this.gestureAnimationDismissing = false;
  1754. });
  1755. }
  1756. /**
  1757. * Dismiss the modal overlay after it has been presented.
  1758. *
  1759. * @param data Any data to emit in the dismiss events.
  1760. * @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'.
  1761. *
  1762. * This is a no-op if the overlay has not been presented yet. If you want
  1763. * to remove an overlay from the DOM that was never presented, use the
  1764. * [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
  1765. */
  1766. async dismiss(data, role) {
  1767. var _a;
  1768. if (this.gestureAnimationDismissing && role !== GESTURE) {
  1769. return false;
  1770. }
  1771. /**
  1772. * Because the canDismiss check below is async,
  1773. * we need to claim a lock before the check happens,
  1774. * in case the dismiss transition does run.
  1775. */
  1776. const unlock = await this.lockController.lock();
  1777. /**
  1778. * If a canDismiss handler is responsible
  1779. * for calling the dismiss method, we should
  1780. * not run the canDismiss check again.
  1781. */
  1782. if (role !== 'handler' && !(await this.checkCanDismiss(data, role))) {
  1783. unlock();
  1784. return false;
  1785. }
  1786. const { presentingElement } = this;
  1787. /**
  1788. * We need to start the status bar change
  1789. * before the animation so that the change
  1790. * finishes when the dismiss animation does.
  1791. */
  1792. const hasCardModal = presentingElement !== undefined;
  1793. if (hasCardModal && getIonMode(this) === 'ios') {
  1794. setCardStatusBarDefault(this.statusBarStyle);
  1795. }
  1796. /* tslint:disable-next-line */
  1797. if (typeof window !== 'undefined' && this.keyboardOpenCallback) {
  1798. window.removeEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
  1799. this.keyboardOpenCallback = undefined;
  1800. }
  1801. const dismissed = await dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, {
  1802. presentingEl: presentingElement,
  1803. currentBreakpoint: (_a = this.currentBreakpoint) !== null && _a !== void 0 ? _a : this.initialBreakpoint,
  1804. backdropBreakpoint: this.backdropBreakpoint,
  1805. expandToScroll: this.expandToScroll,
  1806. });
  1807. if (dismissed) {
  1808. const { delegate } = this.getDelegate();
  1809. await detachComponent(delegate, this.usersElement);
  1810. writeTask(() => this.el.classList.remove('show-modal'));
  1811. if (this.animation) {
  1812. this.animation.destroy();
  1813. }
  1814. if (this.gesture) {
  1815. this.gesture.destroy();
  1816. }
  1817. }
  1818. this.currentBreakpoint = undefined;
  1819. this.animation = undefined;
  1820. unlock();
  1821. return dismissed;
  1822. }
  1823. /**
  1824. * Returns a promise that resolves when the modal did dismiss.
  1825. */
  1826. onDidDismiss() {
  1827. return eventMethod(this.el, 'ionModalDidDismiss');
  1828. }
  1829. /**
  1830. * Returns a promise that resolves when the modal will dismiss.
  1831. */
  1832. onWillDismiss() {
  1833. return eventMethod(this.el, 'ionModalWillDismiss');
  1834. }
  1835. /**
  1836. * Move a sheet style modal to a specific breakpoint. The breakpoint value must
  1837. * be a value defined in your `breakpoints` array.
  1838. */
  1839. async setCurrentBreakpoint(breakpoint) {
  1840. if (!this.isSheetModal) {
  1841. printIonWarning('[ion-modal] - setCurrentBreakpoint is only supported on sheet modals.');
  1842. return;
  1843. }
  1844. if (!this.breakpoints.includes(breakpoint)) {
  1845. printIonWarning(`[ion-modal] - Attempted to set invalid breakpoint value ${breakpoint}. Please double check that the breakpoint value is part of your defined breakpoints.`);
  1846. return;
  1847. }
  1848. const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints, animated } = this;
  1849. if (currentBreakpoint === breakpoint) {
  1850. return;
  1851. }
  1852. if (moveSheetToBreakpoint) {
  1853. this.sheetTransition = moveSheetToBreakpoint({
  1854. breakpoint,
  1855. breakpointOffset: 1 - currentBreakpoint,
  1856. canDismiss: canDismiss !== undefined && canDismiss !== true && breakpoints[0] === 0,
  1857. animated,
  1858. });
  1859. await this.sheetTransition;
  1860. this.sheetTransition = undefined;
  1861. }
  1862. }
  1863. /**
  1864. * Returns the current breakpoint of a sheet style modal
  1865. */
  1866. async getCurrentBreakpoint() {
  1867. return this.currentBreakpoint;
  1868. }
  1869. async moveToNextBreakpoint() {
  1870. const { breakpoints, currentBreakpoint } = this;
  1871. if (!breakpoints || currentBreakpoint == null) {
  1872. /**
  1873. * If the modal does not have breakpoints and/or the current
  1874. * breakpoint is not set, we can't move to the next breakpoint.
  1875. */
  1876. return false;
  1877. }
  1878. const allowedBreakpoints = breakpoints.filter((b) => b !== 0);
  1879. const currentBreakpointIndex = allowedBreakpoints.indexOf(currentBreakpoint);
  1880. const nextBreakpointIndex = (currentBreakpointIndex + 1) % allowedBreakpoints.length;
  1881. const nextBreakpoint = allowedBreakpoints[nextBreakpointIndex];
  1882. /**
  1883. * Sets the current breakpoint to the next available breakpoint.
  1884. * If the current breakpoint is the last breakpoint, we set the current
  1885. * breakpoint to the first non-zero breakpoint to avoid dismissing the sheet.
  1886. */
  1887. await this.setCurrentBreakpoint(nextBreakpoint);
  1888. return true;
  1889. }
  1890. render() {
  1891. const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
  1892. const showHandle = handle !== false && isSheetModal;
  1893. const mode = getIonMode(this);
  1894. const isCardModal = presentingElement !== undefined && mode === 'ios';
  1895. const isHandleCycle = handleBehavior === 'cycle';
  1896. return (h(Host, Object.assign({ key: '0991b2e4e32da511e59fb1463b47e4ac1b86d1ca', "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
  1897. zIndex: `${20000 + this.overlayIndex}`,
  1898. }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle }), h("ion-backdrop", { key: 'ca9453ffe1021fb252ad9460676cfabb5633f00f', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '9f8da446a7b0f3b26aec856e13f6d6d131a7e37b', class: "modal-shadow" }), h("div", Object.assign({ key: '9d08bf600571849c97b58f66df40b496a358d1e1',
  1899. /*
  1900. role and aria-modal must be used on the
  1901. same element. They must also be set inside the
  1902. shadow DOM otherwise ion-button will not be highlighted
  1903. when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
  1904. */
  1905. role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'f8bf0d1126e5376519101225d9965727121ee042', class: "modal-handle",
  1906. // Prevents the handle from receiving keyboard focus when it does not cycle
  1907. tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle" })), h("slot", { key: '6d52849df98f2c6c8fbc03996a931ea6a39a512b' }))));
  1908. }
  1909. get el() { return getElement(this); }
  1910. static get watchers() { return {
  1911. "isOpen": ["onIsOpenChange"],
  1912. "trigger": ["triggerChanged"]
  1913. }; }
  1914. };
  1915. const LIFECYCLE_MAP = {
  1916. ionModalDidPresent: 'ionViewDidEnter',
  1917. ionModalWillPresent: 'ionViewWillEnter',
  1918. ionModalWillDismiss: 'ionViewWillLeave',
  1919. ionModalDidDismiss: 'ionViewDidLeave',
  1920. };
  1921. Modal.style = {
  1922. ios: IonModalIosStyle0,
  1923. md: IonModalMdStyle0
  1924. };
  1925. export { Modal as ion_modal };