modal.js 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { proxyCustomElement, HTMLElement, createEvent, writeTask, h, Host } from '@stencil/core/internal/client';
  5. import { a as findClosestIonContent, i as isIonContent, d as disableContentScrollY, r as resetContentScrollY, f as findIonContent, p as printIonContentErrorMsg } from './index8.js';
  6. import { C as CoreDelegate, a as attachComponent, d as detachComponent } from './framework-delegate.js';
  7. import { g as getElementRoot, k as clamp, r as raf, d as inheritAttributes, j as hasLazyBuild } from './helpers.js';
  8. import { c as createLockController } from './lock-controller.js';
  9. import { a as printIonWarning, c as config } from './index4.js';
  10. import { g as getCapacitor } from './capacitor.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.js';
  12. import { g as getClassMap } from './theme.js';
  13. import { e as deepReady, w as waitForMount } from './index2.js';
  14. import { b as getIonMode } from './ionic-global.js';
  15. import { KEYBOARD_DID_OPEN } from './keyboard2.js';
  16. import { c as createAnimation } from './animation.js';
  17. import { g as getTimeGivenProgression } from './cubic-bezier.js';
  18. import { createGesture } from './index3.js';
  19. import { w as win } from './index6.js';
  20. import { d as defineCustomElement$1 } from './backdrop.js';
  21. var Style;
  22. (function (Style) {
  23. Style["Dark"] = "DARK";
  24. Style["Light"] = "LIGHT";
  25. Style["Default"] = "DEFAULT";
  26. })(Style || (Style = {}));
  27. const StatusBar = {
  28. getEngine() {
  29. const capacitor = getCapacitor();
  30. if (capacitor === null || capacitor === void 0 ? void 0 : capacitor.isPluginAvailable('StatusBar')) {
  31. return capacitor.Plugins.StatusBar;
  32. }
  33. return undefined;
  34. },
  35. setStyle(options) {
  36. const engine = this.getEngine();
  37. if (!engine) {
  38. return;
  39. }
  40. engine.setStyle(options);
  41. },
  42. getStyle: async function () {
  43. const engine = this.getEngine();
  44. if (!engine) {
  45. return Style.Default;
  46. }
  47. const { style } = await engine.getInfo();
  48. return style;
  49. },
  50. };
  51. /**
  52. * Use y = mx + b to
  53. * figure out the backdrop value
  54. * at a particular x coordinate. This
  55. * is useful when the backdrop does
  56. * not begin to fade in until after
  57. * the 0 breakpoint.
  58. */
  59. const getBackdropValueForSheet = (x, backdropBreakpoint) => {
  60. /**
  61. * We will use these points:
  62. * (backdropBreakpoint, 0)
  63. * (maxBreakpoint, 1)
  64. * We know that at the beginning breakpoint,
  65. * the backdrop will be hidden. We also
  66. * know that at the maxBreakpoint, the backdrop
  67. * must be fully visible. maxBreakpoint should
  68. * always be 1 even if the maximum value
  69. * of the breakpoints array is not 1 since
  70. * the animation runs from a progress of 0
  71. * to a progress of 1.
  72. * m = (y2 - y1) / (x2 - x1)
  73. *
  74. * This is simplified from:
  75. * m = (1 - 0) / (maxBreakpoint - backdropBreakpoint)
  76. *
  77. * If the backdropBreakpoint is 1, we return 0 as the
  78. * backdrop is completely hidden.
  79. *
  80. */
  81. if (backdropBreakpoint === 1) {
  82. return 0;
  83. }
  84. const slope = 1 / (1 - backdropBreakpoint);
  85. /**
  86. * From here, compute b which is
  87. * the backdrop opacity if the offset
  88. * is 0. If the backdrop does not
  89. * begin to fade in until after the
  90. * 0 breakpoint, this b value will be
  91. * negative. This is fine as we never pass
  92. * b directly into the animation keyframes.
  93. * b = y - mx
  94. * Use a known point: (backdropBreakpoint, 0)
  95. * This is simplified from:
  96. * b = 0 - (backdropBreakpoint * slope)
  97. */
  98. const b = -(backdropBreakpoint * slope);
  99. /**
  100. * Finally, we can now determine the
  101. * backdrop offset given an arbitrary
  102. * gesture offset.
  103. */
  104. return x * slope + b;
  105. };
  106. /**
  107. * The tablet/desktop card modal activates
  108. * when the window width is >= 768.
  109. * At that point, the presenting element
  110. * is not transformed, so we do not need to
  111. * adjust the status bar color.
  112. *
  113. */
  114. const setCardStatusBarDark = () => {
  115. if (!win || win.innerWidth >= 768) {
  116. return;
  117. }
  118. StatusBar.setStyle({ style: Style.Dark });
  119. };
  120. const setCardStatusBarDefault = (defaultStyle = Style.Default) => {
  121. if (!win || win.innerWidth >= 768) {
  122. return;
  123. }
  124. StatusBar.setStyle({ style: defaultStyle });
  125. };
  126. const handleCanDismiss = async (el, animation) => {
  127. /**
  128. * If canDismiss is not a function
  129. * then we can return early. If canDismiss is `true`,
  130. * then canDismissBlocksGesture is `false` as canDismiss
  131. * will never interrupt the gesture. As a result,
  132. * this code block is never reached. If canDismiss is `false`,
  133. * then we never dismiss.
  134. */
  135. if (typeof el.canDismiss !== 'function') {
  136. return;
  137. }
  138. /**
  139. * Run the canDismiss callback.
  140. * If the function returns `true`,
  141. * then we can proceed with dismiss.
  142. */
  143. const shouldDismiss = await el.canDismiss(undefined, GESTURE);
  144. if (!shouldDismiss) {
  145. return;
  146. }
  147. /**
  148. * If canDismiss resolved after the snap
  149. * back animation finished, we can
  150. * dismiss immediately.
  151. *
  152. * If canDismiss resolved before the snap
  153. * back animation finished, we need to
  154. * wait until the snap back animation is
  155. * done before dismissing.
  156. */
  157. if (animation.isRunning()) {
  158. animation.onFinish(() => {
  159. el.dismiss(undefined, 'handler');
  160. }, { oneTimeCallback: true });
  161. }
  162. else {
  163. el.dismiss(undefined, 'handler');
  164. }
  165. };
  166. /**
  167. * This function lets us simulate a realistic spring-like animation
  168. * when swiping down on the modal.
  169. * There are two forces that we need to use to compute the spring physics:
  170. *
  171. * 1. Stiffness, k: This is a measure of resistance applied a spring.
  172. * 2. Dampening, c: This value has the effect of reducing or preventing oscillation.
  173. *
  174. * Using these two values, we can calculate the Spring Force and the Dampening Force
  175. * to compute the total force applied to a spring.
  176. *
  177. * Spring Force: This force pulls a spring back into its equilibrium position.
  178. * Hooke's Law tells us that that spring force (FS) = kX.
  179. * k is the stiffness of a spring, and X is the displacement of the spring from its
  180. * equilibrium position. In this case, it is the amount by which the free end
  181. * of a spring was displaced (stretched/pushed) from its "relaxed" position.
  182. *
  183. * Dampening Force: This force slows down motion. Without it, a spring would oscillate forever.
  184. * The dampening force, FD, can be found via this formula: FD = -cv
  185. * where c the dampening value and v is velocity.
  186. *
  187. * Therefore, the resulting force that is exerted on the block is:
  188. * F = FS + FD = -kX - cv
  189. *
  190. * Newton's 2nd Law tells us that F = ma:
  191. * ma = -kX - cv.
  192. *
  193. * For Ionic's purposes, we can assume that m = 1:
  194. * a = -kX - cv
  195. *
  196. * Imagine a block attached to the end of a spring. At equilibrium
  197. * the block is at position x = 1.
  198. * Pressing on the block moves it to position x = 0;
  199. * So, to calculate the displacement, we need to take the
  200. * current position and subtract the previous position from it.
  201. * X = x - x0 = 0 - 1 = -1.
  202. *
  203. * For Ionic's purposes, we are only pushing on the spring modal
  204. * so we have a max position of 1.
  205. * As a result, we can expand displacement to this formula:
  206. * X = x - 1
  207. *
  208. * a = -k(x - 1) - cv
  209. *
  210. * We can represent the motion of something as a function of time: f(t) = x.
  211. * The derivative of position gives us the velocity: f'(t)
  212. * The derivative of the velocity gives us the acceleration: f''(t)
  213. *
  214. * We can substitute the formula above with these values:
  215. *
  216. * f"(t) = -k * (f(t) - 1) - c * f'(t)
  217. *
  218. * This is called a differential equation.
  219. *
  220. * We know that at t = 0, we are at x = 0 because the modal does not move: f(0) = 0
  221. * This means our velocity is also zero: f'(0) = 0.
  222. *
  223. * We can cheat a bit and plug the formula into Wolfram Alpha.
  224. * However, we need to pick stiffness and dampening values:
  225. * k = 0.57
  226. * c = 15
  227. *
  228. * I picked these as they are fairly close to native iOS's spring effect
  229. * with the modal.
  230. *
  231. * What we plug in is this: f(0) = 0; f'(0) = 0; f''(t) = -0.57(f(t) - 1) - 15f'(t)
  232. *
  233. * The result is a formula that lets us calculate the acceleration
  234. * for a given time t.
  235. * Note: This is the approximate form of the solution. Wolfram Alpha will
  236. * give you a complex differential equation too.
  237. */
  238. const calculateSpringStep = (t) => {
  239. return 0.00255275 * 2.71828 ** (-14.9619 * t) - 1.00255 * 2.71828 ** (-0.0380968 * t) + 1;
  240. };
  241. // Defaults for the card swipe animation
  242. const SwipeToCloseDefaults = {
  243. MIN_PRESENTING_SCALE: 0.915,
  244. };
  245. const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
  246. /**
  247. * The step value at which a card modal
  248. * is eligible for dismissing via gesture.
  249. */
  250. const DISMISS_THRESHOLD = 0.5;
  251. const height = el.offsetHeight;
  252. let isOpen = false;
  253. let canDismissBlocksGesture = false;
  254. let contentEl = null;
  255. let scrollEl = null;
  256. const canDismissMaxStep = 0.2;
  257. let initialScrollY = true;
  258. let lastStep = 0;
  259. const getScrollY = () => {
  260. if (contentEl && isIonContent(contentEl)) {
  261. return contentEl.scrollY;
  262. /**
  263. * Custom scroll containers are intended to be
  264. * used with virtual scrolling, so we assume
  265. * there is scrolling in this case.
  266. */
  267. }
  268. else {
  269. return true;
  270. }
  271. };
  272. const canStart = (detail) => {
  273. const target = detail.event.target;
  274. if (target === null || !target.closest) {
  275. return true;
  276. }
  277. /**
  278. * If we are swiping on the content,
  279. * swiping should only be possible if
  280. * the content is scrolled all the way
  281. * to the top so that we do not interfere
  282. * with scrolling.
  283. *
  284. * We cannot assume that the `ion-content`
  285. * target will remain consistent between
  286. * swipes. For example, when using
  287. * ion-nav within a card modal it is
  288. * possible to swipe, push a view, and then
  289. * swipe again. The target content will not
  290. * be the same between swipes.
  291. */
  292. contentEl = findClosestIonContent(target);
  293. if (contentEl) {
  294. /**
  295. * The card should never swipe to close
  296. * on the content with a refresher.
  297. * Note: We cannot solve this by making the
  298. * swipeToClose gesture have a higher priority
  299. * than the refresher gesture as the iOS native
  300. * refresh gesture uses a scroll listener in
  301. * addition to a gesture.
  302. *
  303. * Note: Do not use getScrollElement here
  304. * because we need this to be a synchronous
  305. * operation, and getScrollElement is
  306. * asynchronous.
  307. */
  308. if (isIonContent(contentEl)) {
  309. const root = getElementRoot(contentEl);
  310. scrollEl = root.querySelector('.inner-scroll');
  311. }
  312. else {
  313. scrollEl = contentEl;
  314. }
  315. const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
  316. return !hasRefresherInContent && scrollEl.scrollTop === 0;
  317. }
  318. /**
  319. * Card should be swipeable on all
  320. * parts of the modal except for the footer.
  321. */
  322. const footer = target.closest('ion-footer');
  323. if (footer === null) {
  324. return true;
  325. }
  326. return false;
  327. };
  328. const onStart = (detail) => {
  329. const { deltaY } = detail;
  330. /**
  331. * Get the initial scrollY value so
  332. * that we can correctly reset the scrollY
  333. * prop when the gesture ends.
  334. */
  335. initialScrollY = getScrollY();
  336. /**
  337. * If canDismiss is anything other than `true`
  338. * then users should be able to swipe down
  339. * until a threshold is hit. At that point,
  340. * the card modal should not proceed any further.
  341. * TODO (FW-937)
  342. * Remove undefined check
  343. */
  344. canDismissBlocksGesture = el.canDismiss !== undefined && el.canDismiss !== true;
  345. /**
  346. * If we are pulling down, then
  347. * it is possible we are pulling on the
  348. * content. We do not want scrolling to
  349. * happen at the same time as the gesture.
  350. */
  351. if (deltaY > 0 && contentEl) {
  352. disableContentScrollY(contentEl);
  353. }
  354. animation.progressStart(true, isOpen ? 1 : 0);
  355. };
  356. const onMove = (detail) => {
  357. const { deltaY } = detail;
  358. /**
  359. * If we are pulling down, then
  360. * it is possible we are pulling on the
  361. * content. We do not want scrolling to
  362. * happen at the same time as the gesture.
  363. */
  364. if (deltaY > 0 && contentEl) {
  365. disableContentScrollY(contentEl);
  366. }
  367. /**
  368. * If we are swiping on the content
  369. * then the swipe gesture should only
  370. * happen if we are pulling down.
  371. *
  372. * However, if we pull up and
  373. * then down such that the scroll position
  374. * returns to 0, we should be able to swipe
  375. * the card.
  376. */
  377. const step = detail.deltaY / height;
  378. /**
  379. * Check if user is swiping down and
  380. * if we have a canDismiss value that
  381. * should block the gesture from
  382. * proceeding,
  383. */
  384. const isAttemptingDismissWithCanDismiss = step >= 0 && canDismissBlocksGesture;
  385. /**
  386. * If we are blocking the gesture from dismissing,
  387. * set the max step value so that the sheet cannot be
  388. * completely hidden.
  389. */
  390. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  391. /**
  392. * If we are blocking the gesture from
  393. * dismissing, calculate the spring modifier value
  394. * this will be added to the starting breakpoint
  395. * value to give the gesture a spring-like feeling.
  396. * Note that the starting breakpoint is always 0,
  397. * so we omit adding 0 to the result.
  398. */
  399. const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
  400. const clampedStep = clamp(0.0001, processedStep, maxStep);
  401. animation.progressStep(clampedStep);
  402. /**
  403. * When swiping down half way, the status bar style
  404. * should be reset to its default value.
  405. *
  406. * We track lastStep so that we do not fire these
  407. * functions on every onMove, only when the user has
  408. * crossed a certain threshold.
  409. */
  410. if (clampedStep >= DISMISS_THRESHOLD && lastStep < DISMISS_THRESHOLD) {
  411. setCardStatusBarDefault(statusBarStyle);
  412. /**
  413. * However, if we swipe back up, then the
  414. * status bar style should be set to have light
  415. * text on a dark background.
  416. */
  417. }
  418. else if (clampedStep < DISMISS_THRESHOLD && lastStep >= DISMISS_THRESHOLD) {
  419. setCardStatusBarDark();
  420. }
  421. lastStep = clampedStep;
  422. };
  423. const onEnd = (detail) => {
  424. const velocity = detail.velocityY;
  425. const step = detail.deltaY / height;
  426. const isAttemptingDismissWithCanDismiss = step >= 0 && canDismissBlocksGesture;
  427. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  428. const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
  429. const clampedStep = clamp(0.0001, processedStep, maxStep);
  430. const threshold = (detail.deltaY + velocity * 1000) / height;
  431. /**
  432. * If canDismiss blocks
  433. * the swipe gesture, then the
  434. * animation can never complete until
  435. * canDismiss is checked.
  436. */
  437. const shouldComplete = !isAttemptingDismissWithCanDismiss && threshold >= DISMISS_THRESHOLD;
  438. let newStepValue = shouldComplete ? -0.001 : 0.001;
  439. if (!shouldComplete) {
  440. animation.easing('cubic-bezier(1, 0, 0.68, 0.28)');
  441. newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], clampedStep)[0];
  442. }
  443. else {
  444. animation.easing('cubic-bezier(0.32, 0.72, 0, 1)');
  445. newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], clampedStep)[0];
  446. }
  447. const duration = shouldComplete
  448. ? computeDuration(step * height, velocity)
  449. : computeDuration((1 - clampedStep) * height, velocity);
  450. isOpen = shouldComplete;
  451. gesture.enable(false);
  452. if (contentEl) {
  453. resetContentScrollY(contentEl, initialScrollY);
  454. }
  455. animation
  456. .onFinish(() => {
  457. if (!shouldComplete) {
  458. gesture.enable(true);
  459. }
  460. })
  461. .progressEnd(shouldComplete ? 1 : 0, newStepValue, duration);
  462. /**
  463. * If the canDismiss value blocked the gesture
  464. * from proceeding, then we should ignore whatever
  465. * shouldComplete is. Whether or not the modal
  466. * animation should complete is now determined by
  467. * canDismiss.
  468. *
  469. * If the user swiped >25% of the way
  470. * to the max step, then we should
  471. * check canDismiss. 25% was chosen
  472. * to avoid accidental swipes.
  473. */
  474. if (isAttemptingDismissWithCanDismiss && clampedStep > maxStep / 4) {
  475. handleCanDismiss(el, animation);
  476. }
  477. else if (shouldComplete) {
  478. onDismiss();
  479. }
  480. };
  481. const gesture = createGesture({
  482. el,
  483. gestureName: 'modalSwipeToClose',
  484. gesturePriority: OVERLAY_GESTURE_PRIORITY,
  485. direction: 'y',
  486. threshold: 10,
  487. canStart,
  488. onStart,
  489. onMove,
  490. onEnd,
  491. });
  492. return gesture;
  493. };
  494. const computeDuration = (remaining, velocity) => {
  495. return clamp(400, remaining / Math.abs(velocity * 1.1), 500);
  496. };
  497. const createSheetEnterAnimation = (opts) => {
  498. const { currentBreakpoint, backdropBreakpoint, expandToScroll } = opts;
  499. /**
  500. * If the backdropBreakpoint is undefined, then the backdrop
  501. * should always fade in. If the backdropBreakpoint came before the
  502. * current breakpoint, then the backdrop should be fading in.
  503. */
  504. const shouldShowBackdrop = backdropBreakpoint === undefined || backdropBreakpoint < currentBreakpoint;
  505. const initialBackdrop = shouldShowBackdrop ? `calc(var(--backdrop-opacity) * ${currentBreakpoint})` : '0';
  506. const backdropAnimation = createAnimation('backdropAnimation').fromTo('opacity', 0, initialBackdrop);
  507. if (shouldShowBackdrop) {
  508. backdropAnimation
  509. .beforeStyles({
  510. 'pointer-events': 'none',
  511. })
  512. .afterClearStyles(['pointer-events']);
  513. }
  514. const wrapperAnimation = createAnimation('wrapperAnimation').keyframes([
  515. { offset: 0, opacity: 1, transform: 'translateY(100%)' },
  516. { offset: 1, opacity: 1, transform: `translateY(${100 - currentBreakpoint * 100}%)` },
  517. ]);
  518. /**
  519. * This allows the content to be scrollable at any breakpoint.
  520. */
  521. const contentAnimation = !expandToScroll
  522. ? createAnimation('contentAnimation').keyframes([
  523. { offset: 0, opacity: 1, maxHeight: `${(1 - currentBreakpoint) * 100}%` },
  524. { offset: 1, opacity: 1, maxHeight: `${currentBreakpoint * 100}%` },
  525. ])
  526. : undefined;
  527. return { wrapperAnimation, backdropAnimation, contentAnimation };
  528. };
  529. const createSheetLeaveAnimation = (opts) => {
  530. const { currentBreakpoint, backdropBreakpoint } = opts;
  531. /**
  532. * Backdrop does not always fade in from 0 to 1 if backdropBreakpoint
  533. * is defined, so we need to account for that offset by figuring out
  534. * what the current backdrop value should be.
  535. */
  536. const backdropValue = `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(currentBreakpoint, backdropBreakpoint)})`;
  537. const defaultBackdrop = [
  538. { offset: 0, opacity: backdropValue },
  539. { offset: 1, opacity: 0 },
  540. ];
  541. const customBackdrop = [
  542. { offset: 0, opacity: backdropValue },
  543. { offset: backdropBreakpoint, opacity: 0 },
  544. { offset: 1, opacity: 0 },
  545. ];
  546. const backdropAnimation = createAnimation('backdropAnimation').keyframes(backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop);
  547. const wrapperAnimation = createAnimation('wrapperAnimation').keyframes([
  548. { offset: 0, opacity: 1, transform: `translateY(${100 - currentBreakpoint * 100}%)` },
  549. { offset: 1, opacity: 1, transform: `translateY(100%)` },
  550. ]);
  551. return { wrapperAnimation, backdropAnimation };
  552. };
  553. const createEnterAnimation$1 = () => {
  554. const backdropAnimation = createAnimation()
  555. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  556. .beforeStyles({
  557. 'pointer-events': 'none',
  558. })
  559. .afterClearStyles(['pointer-events']);
  560. const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
  561. return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
  562. };
  563. /**
  564. * iOS Modal Enter Animation for the Card presentation style
  565. */
  566. const iosEnterAnimation = (baseEl, opts) => {
  567. const { presentingEl, currentBreakpoint, expandToScroll } = opts;
  568. const root = getElementRoot(baseEl);
  569. const { wrapperAnimation, backdropAnimation, contentAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation$1();
  570. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  571. wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')).beforeStyles({ opacity: 1 });
  572. // The content animation is only added if scrolling is enabled for
  573. // all the breakpoints.
  574. !expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
  575. const baseAnimation = createAnimation('entering-base')
  576. .addElement(baseEl)
  577. .easing('cubic-bezier(0.32,0.72,0,1)')
  578. .duration(500)
  579. .addAnimation([wrapperAnimation])
  580. .beforeAddWrite(() => {
  581. if (expandToScroll) {
  582. // Scroll can only be done when the modal is fully expanded.
  583. return;
  584. }
  585. /**
  586. * There are some browsers that causes flickering when
  587. * dragging the content when scroll is enabled at every
  588. * breakpoint. This is due to the wrapper element being
  589. * transformed off the screen and having a snap animation.
  590. *
  591. * A workaround is to clone the footer element and append
  592. * it outside of the wrapper element. This way, the footer
  593. * is still visible and the drag can be done without
  594. * flickering. The original footer is hidden until the modal
  595. * is dismissed. This maintains the animation of the footer
  596. * when the modal is dismissed.
  597. *
  598. * The workaround needs to be done before the animation starts
  599. * so there are no flickering issues.
  600. */
  601. const ionFooter = baseEl.querySelector('ion-footer');
  602. /**
  603. * This check is needed to prevent more than one footer
  604. * from being appended to the shadow root.
  605. * Otherwise, iOS and MD enter animations would append
  606. * the footer twice.
  607. */
  608. const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
  609. if (ionFooter && !ionFooterAlreadyAppended) {
  610. const footerHeight = ionFooter.clientHeight;
  611. const clonedFooter = ionFooter.cloneNode(true);
  612. baseEl.shadowRoot.appendChild(clonedFooter);
  613. ionFooter.style.setProperty('display', 'none');
  614. ionFooter.setAttribute('aria-hidden', 'true');
  615. // Padding is added to prevent some content from being hidden.
  616. const page = baseEl.querySelector('.ion-page');
  617. page.style.setProperty('padding-bottom', `${footerHeight}px`);
  618. }
  619. });
  620. if (contentAnimation) {
  621. baseAnimation.addAnimation(contentAnimation);
  622. }
  623. if (presentingEl) {
  624. const isMobile = window.innerWidth < 768;
  625. const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
  626. const presentingElRoot = getElementRoot(presentingEl);
  627. const presentingAnimation = createAnimation().beforeStyles({
  628. transform: 'translateY(0)',
  629. 'transform-origin': 'top center',
  630. overflow: 'hidden',
  631. });
  632. const bodyEl = document.body;
  633. if (isMobile) {
  634. /**
  635. * Fallback for browsers that does not support `max()` (ex: Firefox)
  636. * No need to worry about statusbar padding since engines like Gecko
  637. * are not used as the engine for standalone Cordova/Capacitor apps
  638. */
  639. const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
  640. const modalTransform = hasCardModal ? '-10px' : transformOffset;
  641. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
  642. const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
  643. presentingAnimation
  644. .afterStyles({
  645. transform: finalTransform,
  646. })
  647. .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
  648. .addElement(presentingEl)
  649. .keyframes([
  650. { offset: 0, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' },
  651. { offset: 1, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
  652. ]);
  653. baseAnimation.addAnimation(presentingAnimation);
  654. }
  655. else {
  656. baseAnimation.addAnimation(backdropAnimation);
  657. if (!hasCardModal) {
  658. wrapperAnimation.fromTo('opacity', '0', '1');
  659. }
  660. else {
  661. const toPresentingScale = hasCardModal ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
  662. const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
  663. presentingAnimation
  664. .afterStyles({
  665. transform: finalTransform,
  666. })
  667. .addElement(presentingElRoot.querySelector('.modal-wrapper'))
  668. .keyframes([
  669. { offset: 0, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' },
  670. { offset: 1, filter: 'contrast(0.85)', transform: finalTransform },
  671. ]);
  672. const shadowAnimation = createAnimation()
  673. .afterStyles({
  674. transform: finalTransform,
  675. })
  676. .addElement(presentingElRoot.querySelector('.modal-shadow'))
  677. .keyframes([
  678. { offset: 0, opacity: '1', transform: 'translateY(0) scale(1)' },
  679. { offset: 1, opacity: '0', transform: finalTransform },
  680. ]);
  681. baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
  682. }
  683. }
  684. }
  685. else {
  686. baseAnimation.addAnimation(backdropAnimation);
  687. }
  688. return baseAnimation;
  689. };
  690. const createLeaveAnimation$1 = () => {
  691. const backdropAnimation = createAnimation().fromTo('opacity', 'var(--backdrop-opacity)', 0);
  692. const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
  693. return { backdropAnimation, wrapperAnimation };
  694. };
  695. /**
  696. * iOS Modal Leave Animation
  697. */
  698. const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
  699. const { presentingEl, currentBreakpoint, expandToScroll } = opts;
  700. const root = getElementRoot(baseEl);
  701. const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
  702. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  703. wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')).beforeStyles({ opacity: 1 });
  704. const baseAnimation = createAnimation('leaving-base')
  705. .addElement(baseEl)
  706. .easing('cubic-bezier(0.32,0.72,0,1)')
  707. .duration(duration)
  708. .addAnimation(wrapperAnimation)
  709. .beforeAddWrite(() => {
  710. if (expandToScroll) {
  711. // Scroll can only be done when the modal is fully expanded.
  712. return;
  713. }
  714. /**
  715. * If expandToScroll is disabled, we need to swap
  716. * the visibility to the original, so the footer
  717. * dismisses with the modal and doesn't stay
  718. * until the modal is removed from the DOM.
  719. */
  720. const ionFooter = baseEl.querySelector('ion-footer');
  721. if (ionFooter) {
  722. const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
  723. ionFooter.style.removeProperty('display');
  724. ionFooter.removeAttribute('aria-hidden');
  725. clonedFooter.style.setProperty('display', 'none');
  726. clonedFooter.setAttribute('aria-hidden', 'true');
  727. const page = baseEl.querySelector('.ion-page');
  728. page.style.removeProperty('padding-bottom');
  729. }
  730. });
  731. if (presentingEl) {
  732. const isMobile = window.innerWidth < 768;
  733. const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
  734. const presentingElRoot = getElementRoot(presentingEl);
  735. const presentingAnimation = createAnimation()
  736. .beforeClearStyles(['transform'])
  737. .afterClearStyles(['transform'])
  738. .onFinish((currentStep) => {
  739. // only reset background color if this is the last card-style modal
  740. if (currentStep !== 1) {
  741. return;
  742. }
  743. presentingEl.style.setProperty('overflow', '');
  744. const numModals = Array.from(bodyEl.querySelectorAll('ion-modal:not(.overlay-hidden)')).filter((m) => m.presentingElement !== undefined).length;
  745. if (numModals <= 1) {
  746. bodyEl.style.setProperty('background-color', '');
  747. }
  748. });
  749. const bodyEl = document.body;
  750. if (isMobile) {
  751. const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
  752. const modalTransform = hasCardModal ? '-10px' : transformOffset;
  753. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
  754. const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
  755. presentingAnimation.addElement(presentingEl).keyframes([
  756. { offset: 0, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
  757. { offset: 1, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' },
  758. ]);
  759. baseAnimation.addAnimation(presentingAnimation);
  760. }
  761. else {
  762. baseAnimation.addAnimation(backdropAnimation);
  763. if (!hasCardModal) {
  764. wrapperAnimation.fromTo('opacity', '1', '0');
  765. }
  766. else {
  767. const toPresentingScale = hasCardModal ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
  768. const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
  769. presentingAnimation
  770. .addElement(presentingElRoot.querySelector('.modal-wrapper'))
  771. .afterStyles({
  772. transform: 'translate3d(0, 0, 0)',
  773. })
  774. .keyframes([
  775. { offset: 0, filter: 'contrast(0.85)', transform: finalTransform },
  776. { offset: 1, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' },
  777. ]);
  778. const shadowAnimation = createAnimation()
  779. .addElement(presentingElRoot.querySelector('.modal-shadow'))
  780. .afterStyles({
  781. transform: 'translateY(0) scale(1)',
  782. })
  783. .keyframes([
  784. { offset: 0, opacity: '0', transform: finalTransform },
  785. { offset: 1, opacity: '1', transform: 'translateY(0) scale(1)' },
  786. ]);
  787. baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
  788. }
  789. }
  790. }
  791. else {
  792. baseAnimation.addAnimation(backdropAnimation);
  793. }
  794. return baseAnimation;
  795. };
  796. const createEnterAnimation = () => {
  797. const backdropAnimation = createAnimation()
  798. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  799. .beforeStyles({
  800. 'pointer-events': 'none',
  801. })
  802. .afterClearStyles(['pointer-events']);
  803. const wrapperAnimation = createAnimation().keyframes([
  804. { offset: 0, opacity: 0.01, transform: 'translateY(40px)' },
  805. { offset: 1, opacity: 1, transform: `translateY(0px)` },
  806. ]);
  807. return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
  808. };
  809. /**
  810. * Md Modal Enter Animation
  811. */
  812. const mdEnterAnimation = (baseEl, opts) => {
  813. const { currentBreakpoint, expandToScroll } = opts;
  814. const root = getElementRoot(baseEl);
  815. const { wrapperAnimation, backdropAnimation, contentAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
  816. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  817. wrapperAnimation.addElement(root.querySelector('.modal-wrapper'));
  818. // The content animation is only added if scrolling is enabled for
  819. // all the breakpoints.
  820. expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
  821. const baseAnimation = createAnimation()
  822. .addElement(baseEl)
  823. .easing('cubic-bezier(0.36,0.66,0.04,1)')
  824. .duration(280)
  825. .addAnimation([backdropAnimation, wrapperAnimation])
  826. .beforeAddWrite(() => {
  827. if (expandToScroll) {
  828. // Scroll can only be done when the modal is fully expanded.
  829. return;
  830. }
  831. /**
  832. * There are some browsers that causes flickering when
  833. * dragging the content when scroll is enabled at every
  834. * breakpoint. This is due to the wrapper element being
  835. * transformed off the screen and having a snap animation.
  836. *
  837. * A workaround is to clone the footer element and append
  838. * it outside of the wrapper element. This way, the footer
  839. * is still visible and the drag can be done without
  840. * flickering. The original footer is hidden until the modal
  841. * is dismissed. This maintains the animation of the footer
  842. * when the modal is dismissed.
  843. *
  844. * The workaround needs to be done before the animation starts
  845. * so there are no flickering issues.
  846. */
  847. const ionFooter = baseEl.querySelector('ion-footer');
  848. /**
  849. * This check is needed to prevent more than one footer
  850. * from being appended to the shadow root.
  851. * Otherwise, iOS and MD enter animations would append
  852. * the footer twice.
  853. */
  854. const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
  855. if (ionFooter && !ionFooterAlreadyAppended) {
  856. const footerHeight = ionFooter.clientHeight;
  857. const clonedFooter = ionFooter.cloneNode(true);
  858. baseEl.shadowRoot.appendChild(clonedFooter);
  859. ionFooter.style.setProperty('display', 'none');
  860. ionFooter.setAttribute('aria-hidden', 'true');
  861. // Padding is added to prevent some content from being hidden.
  862. const page = baseEl.querySelector('.ion-page');
  863. page.style.setProperty('padding-bottom', `${footerHeight}px`);
  864. }
  865. });
  866. if (contentAnimation) {
  867. baseAnimation.addAnimation(contentAnimation);
  868. }
  869. return baseAnimation;
  870. };
  871. const createLeaveAnimation = () => {
  872. const backdropAnimation = createAnimation().fromTo('opacity', 'var(--backdrop-opacity)', 0);
  873. const wrapperAnimation = createAnimation().keyframes([
  874. { offset: 0, opacity: 0.99, transform: `translateY(0px)` },
  875. { offset: 1, opacity: 0, transform: 'translateY(40px)' },
  876. ]);
  877. return { backdropAnimation, wrapperAnimation };
  878. };
  879. /**
  880. * Md Modal Leave Animation
  881. */
  882. const mdLeaveAnimation = (baseEl, opts) => {
  883. const { currentBreakpoint, expandToScroll } = opts;
  884. const root = getElementRoot(baseEl);
  885. const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
  886. backdropAnimation.addElement(root.querySelector('ion-backdrop'));
  887. wrapperAnimation.addElement(root.querySelector('.modal-wrapper'));
  888. const baseAnimation = createAnimation()
  889. .easing('cubic-bezier(0.47,0,0.745,0.715)')
  890. .duration(200)
  891. .addAnimation([backdropAnimation, wrapperAnimation])
  892. .beforeAddWrite(() => {
  893. if (expandToScroll) {
  894. // Scroll can only be done when the modal is fully expanded.
  895. return;
  896. }
  897. /**
  898. * If expandToScroll is disabled, we need to swap
  899. * the visibility to the original, so the footer
  900. * dismisses with the modal and doesn't stay
  901. * until the modal is removed from the DOM.
  902. */
  903. const ionFooter = baseEl.querySelector('ion-footer');
  904. if (ionFooter) {
  905. const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
  906. ionFooter.style.removeProperty('display');
  907. ionFooter.removeAttribute('aria-hidden');
  908. clonedFooter.style.setProperty('display', 'none');
  909. clonedFooter.setAttribute('aria-hidden', 'true');
  910. const page = baseEl.querySelector('.ion-page');
  911. page.style.removeProperty('padding-bottom');
  912. }
  913. });
  914. return baseAnimation;
  915. };
  916. const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
  917. // Defaults for the sheet swipe animation
  918. const defaultBackdrop = [
  919. { offset: 0, opacity: 'var(--backdrop-opacity)' },
  920. { offset: 1, opacity: 0.01 },
  921. ];
  922. const customBackdrop = [
  923. { offset: 0, opacity: 'var(--backdrop-opacity)' },
  924. { offset: 1 - backdropBreakpoint, opacity: 0 },
  925. { offset: 1, opacity: 0 },
  926. ];
  927. const SheetDefaults = {
  928. WRAPPER_KEYFRAMES: [
  929. { offset: 0, transform: 'translateY(0%)' },
  930. { offset: 1, transform: 'translateY(100%)' },
  931. ],
  932. BACKDROP_KEYFRAMES: backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop,
  933. CONTENT_KEYFRAMES: [
  934. { offset: 0, maxHeight: '100%' },
  935. { offset: 1, maxHeight: '0%' },
  936. ],
  937. };
  938. const contentEl = baseEl.querySelector('ion-content');
  939. const height = wrapperEl.clientHeight;
  940. let currentBreakpoint = initialBreakpoint;
  941. let offset = 0;
  942. let canDismissBlocksGesture = false;
  943. let cachedScrollEl = null;
  944. const canDismissMaxStep = 0.95;
  945. const maxBreakpoint = breakpoints[breakpoints.length - 1];
  946. const minBreakpoint = breakpoints[0];
  947. const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
  948. const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
  949. const contentAnimation = animation.childAnimations.find((ani) => ani.id === 'contentAnimation');
  950. const enableBackdrop = () => {
  951. baseEl.style.setProperty('pointer-events', 'auto');
  952. backdropEl.style.setProperty('pointer-events', 'auto');
  953. /**
  954. * When the backdrop is enabled, elements such
  955. * as inputs should not be focusable outside
  956. * the sheet.
  957. */
  958. baseEl.classList.remove(FOCUS_TRAP_DISABLE_CLASS);
  959. };
  960. const disableBackdrop = () => {
  961. baseEl.style.setProperty('pointer-events', 'none');
  962. backdropEl.style.setProperty('pointer-events', 'none');
  963. /**
  964. * When the backdrop is enabled, elements such
  965. * as inputs should not be focusable outside
  966. * the sheet.
  967. * Adding this class disables focus trapping
  968. * for the sheet temporarily.
  969. */
  970. baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
  971. };
  972. /**
  973. * Toggles the visible modal footer when `expandToScroll` is disabled.
  974. * @param footer The footer to show.
  975. */
  976. const swapFooterVisibility = (footer) => {
  977. const originalFooter = baseEl.querySelector('ion-footer');
  978. if (!originalFooter) {
  979. return;
  980. }
  981. const clonedFooter = wrapperEl.nextElementSibling;
  982. const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
  983. const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
  984. footerToShow.style.removeProperty('display');
  985. footerToShow.removeAttribute('aria-hidden');
  986. const page = baseEl.querySelector('.ion-page');
  987. if (footer === 'original') {
  988. page.style.removeProperty('padding-bottom');
  989. }
  990. else {
  991. const pagePadding = footerToShow.clientHeight;
  992. page.style.setProperty('padding-bottom', `${pagePadding}px`);
  993. }
  994. footerToHide.style.setProperty('display', 'none');
  995. footerToHide.setAttribute('aria-hidden', 'true');
  996. };
  997. /**
  998. * After the entering animation completes,
  999. * we need to set the animation to go from
  1000. * offset 0 to offset 1 so that users can
  1001. * swipe in any direction. We then set the
  1002. * animation offset to the current breakpoint
  1003. * so there is no flickering.
  1004. */
  1005. if (wrapperAnimation && backdropAnimation) {
  1006. wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
  1007. backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
  1008. contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
  1009. animation.progressStart(true, 1 - currentBreakpoint);
  1010. /**
  1011. * If backdrop is not enabled, then content
  1012. * behind modal should be clickable. To do this, we need
  1013. * to remove pointer-events from ion-modal as a whole.
  1014. * ion-backdrop and .modal-wrapper always have pointer-events: auto
  1015. * applied, so the modal content can still be interacted with.
  1016. */
  1017. const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
  1018. if (shouldEnableBackdrop) {
  1019. enableBackdrop();
  1020. }
  1021. else {
  1022. disableBackdrop();
  1023. }
  1024. }
  1025. if (contentEl && currentBreakpoint !== maxBreakpoint && expandToScroll) {
  1026. contentEl.scrollY = false;
  1027. }
  1028. const canStart = (detail) => {
  1029. /**
  1030. * If we are swiping on the content, swiping should only be possible if the content
  1031. * is scrolled all the way to the top so that we do not interfere with scrolling.
  1032. *
  1033. * We cannot assume that the `ion-content` target will remain consistent between swipes.
  1034. * For example, when using ion-nav within a modal it is possible to swipe, push a view,
  1035. * and then swipe again. The target content will not be the same between swipes.
  1036. */
  1037. const contentEl = findClosestIonContent(detail.event.target);
  1038. currentBreakpoint = getCurrentBreakpoint();
  1039. /**
  1040. * If `expandToScroll` is disabled, we should not allow the swipe gesture
  1041. * to start if the content is not scrolled to the top.
  1042. */
  1043. if (!expandToScroll && contentEl) {
  1044. const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
  1045. return scrollEl.scrollTop === 0;
  1046. }
  1047. if (currentBreakpoint === 1 && contentEl) {
  1048. /**
  1049. * The modal should never swipe to close on the content with a refresher.
  1050. * Note 1: We cannot solve this by making this gesture have a higher priority than
  1051. * the refresher gesture as the iOS native refresh gesture uses a scroll listener in
  1052. * addition to a gesture.
  1053. *
  1054. * Note 2: Do not use getScrollElement here because we need this to be a synchronous
  1055. * operation, and getScrollElement is asynchronous.
  1056. */
  1057. const scrollEl = isIonContent(contentEl) ? getElementRoot(contentEl).querySelector('.inner-scroll') : contentEl;
  1058. const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
  1059. return !hasRefresherInContent && scrollEl.scrollTop === 0;
  1060. }
  1061. return true;
  1062. };
  1063. const onStart = (detail) => {
  1064. /**
  1065. * If canDismiss is anything other than `true`
  1066. * then users should be able to swipe down
  1067. * until a threshold is hit. At that point,
  1068. * the card modal should not proceed any further.
  1069. *
  1070. * canDismiss is never fired via gesture if there is
  1071. * no 0 breakpoint. However, it can be fired if the user
  1072. * presses Esc or the hardware back button.
  1073. * TODO (FW-937)
  1074. * Remove undefined check
  1075. */
  1076. canDismissBlocksGesture = baseEl.canDismiss !== undefined && baseEl.canDismiss !== true && minBreakpoint === 0;
  1077. /**
  1078. * Cache the scroll element reference when the gesture starts,
  1079. * this allows us to avoid querying the DOM for the target in onMove,
  1080. * which would impact performance significantly.
  1081. */
  1082. if (!expandToScroll) {
  1083. const targetEl = findClosestIonContent(detail.event.target);
  1084. cachedScrollEl =
  1085. targetEl && isIonContent(targetEl) ? getElementRoot(targetEl).querySelector('.inner-scroll') : targetEl;
  1086. }
  1087. /**
  1088. * If expandToScroll is disabled, we need to swap
  1089. * the footer visibility to the original, so if the modal
  1090. * is dismissed, the footer dismisses with the modal
  1091. * and doesn't stay on the screen after the modal is gone.
  1092. */
  1093. if (!expandToScroll) {
  1094. swapFooterVisibility('original');
  1095. }
  1096. /**
  1097. * If we are pulling down, then it is possible we are pulling on the content.
  1098. * We do not want scrolling to happen at the same time as the gesture.
  1099. */
  1100. if (detail.deltaY > 0 && contentEl) {
  1101. contentEl.scrollY = false;
  1102. }
  1103. raf(() => {
  1104. /**
  1105. * Dismisses the open keyboard when the sheet drag gesture is started.
  1106. * Sets the focus onto the modal element.
  1107. */
  1108. baseEl.focus();
  1109. });
  1110. animation.progressStart(true, 1 - currentBreakpoint);
  1111. };
  1112. const onMove = (detail) => {
  1113. /**
  1114. * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
  1115. * the scrollable content, we should not allow the swipe gesture to continue.
  1116. */
  1117. if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl) {
  1118. return;
  1119. }
  1120. /**
  1121. * If we are pulling down, then it is possible we are pulling on the content.
  1122. * We do not want scrolling to happen at the same time as the gesture.
  1123. * This accounts for when the user scrolls down, scrolls all the way up, and then
  1124. * pulls down again such that the modal should start to move.
  1125. */
  1126. if (detail.deltaY > 0 && contentEl) {
  1127. contentEl.scrollY = false;
  1128. }
  1129. /**
  1130. * Given the change in gesture position on the Y axis,
  1131. * compute where the offset of the animation should be
  1132. * relative to where the user dragged.
  1133. */
  1134. const initialStep = 1 - currentBreakpoint;
  1135. const secondToLastBreakpoint = breakpoints.length > 1 ? 1 - breakpoints[1] : undefined;
  1136. const step = initialStep + detail.deltaY / height;
  1137. const isAttemptingDismissWithCanDismiss = secondToLastBreakpoint !== undefined && step >= secondToLastBreakpoint && canDismissBlocksGesture;
  1138. /**
  1139. * If we are blocking the gesture from dismissing,
  1140. * set the max step value so that the sheet cannot be
  1141. * completely hidden.
  1142. */
  1143. const maxStep = isAttemptingDismissWithCanDismiss ? canDismissMaxStep : 0.9999;
  1144. /**
  1145. * If we are blocking the gesture from
  1146. * dismissing, calculate the spring modifier value
  1147. * this will be added to the starting breakpoint
  1148. * value to give the gesture a spring-like feeling.
  1149. * Note that when isAttemptingDismissWithCanDismiss is true,
  1150. * the modifier is always added to the breakpoint that
  1151. * appears right after the 0 breakpoint.
  1152. *
  1153. * Note that this modifier is essentially the progression
  1154. * between secondToLastBreakpoint and maxStep which is
  1155. * why we subtract secondToLastBreakpoint. This lets us get
  1156. * the result as a value from 0 to 1.
  1157. */
  1158. const processedStep = isAttemptingDismissWithCanDismiss && secondToLastBreakpoint !== undefined
  1159. ? secondToLastBreakpoint +
  1160. calculateSpringStep((step - secondToLastBreakpoint) / (maxStep - secondToLastBreakpoint))
  1161. : step;
  1162. offset = clamp(0.0001, processedStep, maxStep);
  1163. animation.progressStep(offset);
  1164. };
  1165. const onEnd = (detail) => {
  1166. /**
  1167. * If expandToScroll is disabled, we should not allow the moveSheetToBreakpoint
  1168. * function to be called if the user is trying to swipe content upwards and the content
  1169. * is not scrolled to the top.
  1170. */
  1171. if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
  1172. return;
  1173. }
  1174. /**
  1175. * When the gesture releases, we need to determine
  1176. * the closest breakpoint to snap to.
  1177. */
  1178. const velocity = detail.velocityY;
  1179. const threshold = (detail.deltaY + velocity * 350) / height;
  1180. const diff = currentBreakpoint - threshold;
  1181. const closest = breakpoints.reduce((a, b) => {
  1182. return Math.abs(b - diff) < Math.abs(a - diff) ? b : a;
  1183. });
  1184. moveSheetToBreakpoint({
  1185. breakpoint: closest,
  1186. breakpointOffset: offset,
  1187. canDismiss: canDismissBlocksGesture,
  1188. /**
  1189. * The swipe is user-driven, so we should
  1190. * always animate when the gesture ends.
  1191. */
  1192. animated: true,
  1193. });
  1194. };
  1195. const moveSheetToBreakpoint = (options) => {
  1196. const { breakpoint, canDismiss, breakpointOffset, animated } = options;
  1197. /**
  1198. * canDismiss should only prevent snapping
  1199. * when users are trying to dismiss. If canDismiss
  1200. * is present but the user is trying to swipe upwards,
  1201. * we should allow that to happen,
  1202. */
  1203. const shouldPreventDismiss = canDismiss && breakpoint === 0;
  1204. const snapToBreakpoint = shouldPreventDismiss ? currentBreakpoint : breakpoint;
  1205. const shouldRemainOpen = snapToBreakpoint !== 0;
  1206. currentBreakpoint = 0;
  1207. /**
  1208. * Update the animation so that it plays from
  1209. * the last offset to the closest snap point.
  1210. */
  1211. if (wrapperAnimation && backdropAnimation) {
  1212. wrapperAnimation.keyframes([
  1213. { offset: 0, transform: `translateY(${breakpointOffset * 100}%)` },
  1214. { offset: 1, transform: `translateY(${(1 - snapToBreakpoint) * 100}%)` },
  1215. ]);
  1216. backdropAnimation.keyframes([
  1217. {
  1218. offset: 0,
  1219. opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(1 - breakpointOffset, backdropBreakpoint)})`,
  1220. },
  1221. {
  1222. offset: 1,
  1223. opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(snapToBreakpoint, backdropBreakpoint)})`,
  1224. },
  1225. ]);
  1226. if (contentAnimation) {
  1227. /**
  1228. * The modal content should scroll at any breakpoint when expandToScroll
  1229. * is disabled. In order to do this, the content needs to be completely
  1230. * viewable so scrolling can access everything. Otherwise, the default
  1231. * behavior would show the content off the screen and only allow
  1232. * scrolling when the sheet is fully expanded.
  1233. */
  1234. contentAnimation.keyframes([
  1235. { offset: 0, maxHeight: `${(1 - breakpointOffset) * 100}%` },
  1236. { offset: 1, maxHeight: `${snapToBreakpoint * 100}%` },
  1237. ]);
  1238. }
  1239. animation.progressStep(0);
  1240. }
  1241. /**
  1242. * Gesture should remain disabled until the
  1243. * snapping animation completes.
  1244. */
  1245. gesture.enable(false);
  1246. /**
  1247. * If expandToScroll is disabled, we need to swap
  1248. * the footer visibility to the cloned one so the footer
  1249. * doesn't flicker when the sheet's height is animated.
  1250. */
  1251. if (!expandToScroll && shouldRemainOpen) {
  1252. swapFooterVisibility('cloned');
  1253. }
  1254. if (shouldPreventDismiss) {
  1255. handleCanDismiss(baseEl, animation);
  1256. }
  1257. else if (!shouldRemainOpen) {
  1258. onDismiss();
  1259. }
  1260. /**
  1261. * Enables scrolling immediately if the sheet is about to fully expand
  1262. * or if it allows scrolling at any breakpoint. Without this, there would
  1263. * be a ~500ms delay while the modal animation completes, causing a
  1264. * noticeable lag. Native iOS allows scrolling as soon as the gesture is
  1265. * released, so we align with that behavior.
  1266. */
  1267. if (contentEl && (snapToBreakpoint === breakpoints[breakpoints.length - 1] || !expandToScroll)) {
  1268. contentEl.scrollY = true;
  1269. }
  1270. return new Promise((resolve) => {
  1271. animation
  1272. .onFinish(() => {
  1273. if (shouldRemainOpen) {
  1274. /**
  1275. * Once the snapping animation completes,
  1276. * we need to reset the animation to go
  1277. * from 0 to 1 so users can swipe in any direction.
  1278. * We then set the animation offset to the current
  1279. * breakpoint so that it starts at the snapped position.
  1280. */
  1281. if (wrapperAnimation && backdropAnimation) {
  1282. raf(() => {
  1283. wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
  1284. backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
  1285. contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
  1286. animation.progressStart(true, 1 - snapToBreakpoint);
  1287. currentBreakpoint = snapToBreakpoint;
  1288. onBreakpointChange(currentBreakpoint);
  1289. /**
  1290. * Backdrop should become enabled
  1291. * after the backdropBreakpoint value
  1292. */
  1293. const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
  1294. if (shouldEnableBackdrop) {
  1295. enableBackdrop();
  1296. }
  1297. else {
  1298. disableBackdrop();
  1299. }
  1300. gesture.enable(true);
  1301. resolve();
  1302. });
  1303. }
  1304. else {
  1305. gesture.enable(true);
  1306. resolve();
  1307. }
  1308. }
  1309. else {
  1310. resolve();
  1311. }
  1312. /**
  1313. * This must be a one time callback
  1314. * otherwise a new callback will
  1315. * be added every time onEnd runs.
  1316. */
  1317. }, { oneTimeCallback: true })
  1318. .progressEnd(1, 0, animated ? 500 : 0);
  1319. });
  1320. };
  1321. const gesture = createGesture({
  1322. el: wrapperEl,
  1323. gestureName: 'modalSheet',
  1324. gesturePriority: 40,
  1325. direction: 'y',
  1326. threshold: 10,
  1327. canStart,
  1328. onStart,
  1329. onMove,
  1330. onEnd,
  1331. });
  1332. return {
  1333. gesture,
  1334. moveSheetToBreakpoint,
  1335. };
  1336. };
  1337. 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}";
  1338. const IonModalIosStyle0 = modalIosCss;
  1339. 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}";
  1340. const IonModalMdStyle0 = modalMdCss;
  1341. const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
  1342. constructor() {
  1343. super();
  1344. this.__registerHost();
  1345. this.__attachShadow();
  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 this; }
  1910. static get watchers() { return {
  1911. "isOpen": ["onIsOpenChange"],
  1912. "trigger": ["triggerChanged"]
  1913. }; }
  1914. static get style() { return {
  1915. ios: IonModalIosStyle0,
  1916. md: IonModalMdStyle0
  1917. }; }
  1918. }, [33, "ion-modal", {
  1919. "hasController": [4, "has-controller"],
  1920. "overlayIndex": [2, "overlay-index"],
  1921. "delegate": [16],
  1922. "keyboardClose": [4, "keyboard-close"],
  1923. "enterAnimation": [16],
  1924. "leaveAnimation": [16],
  1925. "breakpoints": [16],
  1926. "expandToScroll": [4, "expand-to-scroll"],
  1927. "initialBreakpoint": [2, "initial-breakpoint"],
  1928. "backdropBreakpoint": [2, "backdrop-breakpoint"],
  1929. "handle": [4],
  1930. "handleBehavior": [1, "handle-behavior"],
  1931. "component": [1],
  1932. "componentProps": [16],
  1933. "cssClass": [1, "css-class"],
  1934. "backdropDismiss": [4, "backdrop-dismiss"],
  1935. "showBackdrop": [4, "show-backdrop"],
  1936. "animated": [4],
  1937. "presentingElement": [16],
  1938. "htmlAttributes": [16],
  1939. "isOpen": [4, "is-open"],
  1940. "trigger": [1],
  1941. "keepContentsMounted": [4, "keep-contents-mounted"],
  1942. "focusTrap": [4, "focus-trap"],
  1943. "canDismiss": [4, "can-dismiss"],
  1944. "presented": [32],
  1945. "present": [64],
  1946. "dismiss": [64],
  1947. "onDidDismiss": [64],
  1948. "onWillDismiss": [64],
  1949. "setCurrentBreakpoint": [64],
  1950. "getCurrentBreakpoint": [64]
  1951. }, undefined, {
  1952. "isOpen": ["onIsOpenChange"],
  1953. "trigger": ["triggerChanged"]
  1954. }]);
  1955. const LIFECYCLE_MAP = {
  1956. ionModalDidPresent: 'ionViewDidEnter',
  1957. ionModalWillPresent: 'ionViewWillEnter',
  1958. ionModalWillDismiss: 'ionViewWillLeave',
  1959. ionModalDidDismiss: 'ionViewDidLeave',
  1960. };
  1961. function defineCustomElement() {
  1962. if (typeof customElements === "undefined") {
  1963. return;
  1964. }
  1965. const components = ["ion-modal", "ion-backdrop"];
  1966. components.forEach(tagName => { switch (tagName) {
  1967. case "ion-modal":
  1968. if (!customElements.get(tagName)) {
  1969. customElements.define(tagName, Modal);
  1970. }
  1971. break;
  1972. case "ion-backdrop":
  1973. if (!customElements.get(tagName)) {
  1974. defineCustomElement$1();
  1975. }
  1976. break;
  1977. } });
  1978. }
  1979. export { Modal as M, defineCustomElement as d };