ion-modal.cjs.entry.js 89 KB

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