ion-toast.cjs.entry.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  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 config = require('./config-f6225ae7.js');
  8. const helpers = require('./helpers-8a48fdea.js');
  9. const lockController = require('./lock-controller-6585a42a.js');
  10. const index$1 = require('./index-cc858e97.js');
  11. const overlays = require('./overlays-4c291a05.js');
  12. const theme = require('./theme-d1c573d2.js');
  13. const ionicGlobal = require('./ionic-global-6dea5a96.js');
  14. const animation = require('./animation-ab2d3449.js');
  15. const index = require('./index-c8d52405.js');
  16. const index$2 = require('./index-ee07ed59.js');
  17. require('./hardware-back-button-3d2b1004.js');
  18. require('./framework-delegate-862d9d00.js');
  19. require('./gesture-controller-9436f482.js');
  20. /**
  21. * Calculate the CSS top and bottom position of the toast, to be used
  22. * as starting points for the animation keyframes.
  23. *
  24. * The default animations for both MD and iOS
  25. * use translateY, which calculates from the
  26. * top edge of the screen. This behavior impacts
  27. * how we compute the offset when a toast has
  28. * position='bottom' since we need to calculate from
  29. * the bottom edge of the screen instead.
  30. *
  31. * @param position The value of the toast's position prop.
  32. * @param positionAnchor The element the toast should be anchored to,
  33. * if applicable.
  34. * @param mode The toast component's mode (md, ios, etc).
  35. * @param toast A reference to the toast element itself.
  36. */
  37. function getAnimationPosition(position, positionAnchor, mode, toast) {
  38. /**
  39. * Start with a predefined offset from the edge the toast will be
  40. * positioned relative to, whether on the screen or anchor element.
  41. */
  42. let offset;
  43. if (mode === 'md') {
  44. offset = position === 'top' ? 8 : -8;
  45. }
  46. else {
  47. offset = position === 'top' ? 10 : -10;
  48. }
  49. /**
  50. * If positionAnchor is defined, add in the distance from the target
  51. * screen edge to the target anchor edge. For position="top", the
  52. * bottom anchor edge is targeted. For position="bottom", the top
  53. * anchor edge is targeted.
  54. */
  55. if (positionAnchor && index.win) {
  56. warnIfAnchorIsHidden(positionAnchor, toast);
  57. const box = positionAnchor.getBoundingClientRect();
  58. if (position === 'top') {
  59. offset += box.bottom;
  60. }
  61. else if (position === 'bottom') {
  62. /**
  63. * Just box.top is the distance from the top edge of the screen
  64. * to the top edge of the anchor. We want to calculate from the
  65. * bottom edge of the screen instead.
  66. */
  67. offset -= index.win.innerHeight - box.top;
  68. }
  69. /**
  70. * We don't include safe area here because that should already be
  71. * accounted for when checking the position of the anchor.
  72. */
  73. return {
  74. top: `${offset}px`,
  75. bottom: `${offset}px`,
  76. };
  77. }
  78. else {
  79. return {
  80. top: `calc(${offset}px + var(--ion-safe-area-top, 0px))`,
  81. bottom: `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`,
  82. };
  83. }
  84. }
  85. /**
  86. * If the anchor element is hidden, getBoundingClientRect()
  87. * will return all 0s for it, which can cause unexpected
  88. * results in the position calculation when animating.
  89. */
  90. function warnIfAnchorIsHidden(positionAnchor, toast) {
  91. if (positionAnchor.offsetParent === null) {
  92. index$1.printIonWarning('[ion-toast] - The positionAnchor element for ion-toast was found in the DOM, but appears to be hidden. This may lead to unexpected positioning of the toast.', toast);
  93. }
  94. }
  95. /**
  96. * Returns the top offset required to place
  97. * the toast in the middle of the screen.
  98. * Only needed when position="toast".
  99. * @param toastHeight - The height of the ion-toast element
  100. * @param wrapperHeight - The height of the .toast-wrapper element
  101. * inside the toast's shadow root.
  102. */
  103. const getOffsetForMiddlePosition = (toastHeight, wrapperHeight) => {
  104. return Math.floor(toastHeight / 2 - wrapperHeight / 2);
  105. };
  106. /**
  107. * iOS Toast Enter Animation
  108. */
  109. const iosEnterAnimation = (baseEl, opts) => {
  110. const baseAnimation = animation.createAnimation();
  111. const wrapperAnimation = animation.createAnimation();
  112. const { position, top, bottom } = opts;
  113. const root = helpers.getElementRoot(baseEl);
  114. const wrapperEl = root.querySelector('.toast-wrapper');
  115. wrapperAnimation.addElement(wrapperEl);
  116. switch (position) {
  117. case 'top':
  118. wrapperAnimation.fromTo('transform', 'translateY(-100%)', `translateY(${top})`);
  119. break;
  120. case 'middle':
  121. const topPosition = getOffsetForMiddlePosition(baseEl.clientHeight, wrapperEl.clientHeight);
  122. wrapperEl.style.top = `${topPosition}px`;
  123. wrapperAnimation.fromTo('opacity', 0.01, 1);
  124. break;
  125. default:
  126. wrapperAnimation.fromTo('transform', 'translateY(100%)', `translateY(${bottom})`);
  127. break;
  128. }
  129. return baseAnimation.easing('cubic-bezier(.155,1.105,.295,1.12)').duration(400).addAnimation(wrapperAnimation);
  130. };
  131. /**
  132. * iOS Toast Leave Animation
  133. */
  134. const iosLeaveAnimation = (baseEl, opts) => {
  135. const baseAnimation = animation.createAnimation();
  136. const wrapperAnimation = animation.createAnimation();
  137. const { position, top, bottom } = opts;
  138. const root = helpers.getElementRoot(baseEl);
  139. const wrapperEl = root.querySelector('.toast-wrapper');
  140. wrapperAnimation.addElement(wrapperEl);
  141. switch (position) {
  142. case 'top':
  143. wrapperAnimation.fromTo('transform', `translateY(${top})`, 'translateY(-100%)');
  144. break;
  145. case 'middle':
  146. wrapperAnimation.fromTo('opacity', 0.99, 0);
  147. break;
  148. default:
  149. wrapperAnimation.fromTo('transform', `translateY(${bottom})`, 'translateY(100%)');
  150. break;
  151. }
  152. return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(300).addAnimation(wrapperAnimation);
  153. };
  154. /**
  155. * MD Toast Enter Animation
  156. */
  157. const mdEnterAnimation = (baseEl, opts) => {
  158. const baseAnimation = animation.createAnimation();
  159. const wrapperAnimation = animation.createAnimation();
  160. const { position, top, bottom } = opts;
  161. const root = helpers.getElementRoot(baseEl);
  162. const wrapperEl = root.querySelector('.toast-wrapper');
  163. wrapperAnimation.addElement(wrapperEl);
  164. switch (position) {
  165. case 'top':
  166. wrapperEl.style.setProperty('transform', `translateY(${top})`);
  167. wrapperAnimation.fromTo('opacity', 0.01, 1);
  168. break;
  169. case 'middle':
  170. const topPosition = getOffsetForMiddlePosition(baseEl.clientHeight, wrapperEl.clientHeight);
  171. wrapperEl.style.top = `${topPosition}px`;
  172. wrapperAnimation.fromTo('opacity', 0.01, 1);
  173. break;
  174. default:
  175. wrapperEl.style.setProperty('transform', `translateY(${bottom})`);
  176. wrapperAnimation.fromTo('opacity', 0.01, 1);
  177. break;
  178. }
  179. return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(400).addAnimation(wrapperAnimation);
  180. };
  181. /**
  182. * md Toast Leave Animation
  183. */
  184. const mdLeaveAnimation = (baseEl) => {
  185. const baseAnimation = animation.createAnimation();
  186. const wrapperAnimation = animation.createAnimation();
  187. const root = helpers.getElementRoot(baseEl);
  188. const wrapperEl = root.querySelector('.toast-wrapper');
  189. wrapperAnimation.addElement(wrapperEl).fromTo('opacity', 0.99, 0);
  190. return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(300).addAnimation(wrapperAnimation);
  191. };
  192. /**
  193. * Create a gesture that allows the Toast
  194. * to be swiped to dismiss.
  195. * @param el - The Toast element
  196. * @param toastPosition - The last computed position of the Toast. This is computed in the "present" method.
  197. * @param onDismiss - A callback to fire when the Toast was swiped to dismiss.
  198. */
  199. const createSwipeToDismissGesture = (el, toastPosition, onDismiss) => {
  200. /**
  201. * Users should swipe on the visible toast wrapper
  202. * rather than on ion-toast which covers the entire screen.
  203. * When testing the class instance the inner wrapper will not
  204. * be defined. As a result, we use a placeholder element in those environments.
  205. */
  206. const wrapperEl = helpers.getElementRoot(el).querySelector('.toast-wrapper');
  207. const hostElHeight = el.clientHeight;
  208. const wrapperElBox = wrapperEl.getBoundingClientRect();
  209. /**
  210. * The maximum amount that
  211. * the toast can be swiped. This should
  212. * account for the wrapper element's height
  213. * too so the toast can be swiped offscreen
  214. * completely.
  215. */
  216. let MAX_SWIPE_DISTANCE = 0;
  217. /**
  218. * The step value at which a toast
  219. * is eligible for dismissing via gesture.
  220. */
  221. const DISMISS_THRESHOLD = 0.5;
  222. /**
  223. * The middle position Toast starts 50% of the way
  224. * through the animation, so we need to use this
  225. * as the starting point for our step values.
  226. */
  227. const STEP_OFFSET = el.position === 'middle' ? 0.5 : 0;
  228. /**
  229. * When the Toast is at the top users will be
  230. * swiping up. As a result, the delta values will be
  231. * negative numbers which will result in negative steps
  232. * and thresholds. As a result, we need to make those numbers
  233. * positive.
  234. */
  235. const INVERSION_FACTOR = el.position === 'top' ? -1 : 1;
  236. /**
  237. * The top offset that places the
  238. * toast in the middle of the screen.
  239. * Only needed when position="middle".
  240. */
  241. const topPosition = getOffsetForMiddlePosition(hostElHeight, wrapperElBox.height);
  242. const SWIPE_UP_DOWN_KEYFRAMES = [
  243. { offset: 0, transform: `translateY(-${topPosition + wrapperElBox.height}px)` },
  244. { offset: 0.5, transform: `translateY(0px)` },
  245. { offset: 1, transform: `translateY(${topPosition + wrapperElBox.height}px)` },
  246. ];
  247. const swipeAnimation = animation.createAnimation('toast-swipe-to-dismiss-animation')
  248. .addElement(wrapperEl)
  249. /**
  250. * The specific value here does not actually
  251. * matter. We just need this to be a positive
  252. * value so the animation does not jump
  253. * to the end when the user beings to drag.
  254. */
  255. .duration(100);
  256. switch (el.position) {
  257. case 'middle':
  258. MAX_SWIPE_DISTANCE = hostElHeight + wrapperElBox.height;
  259. swipeAnimation.keyframes(SWIPE_UP_DOWN_KEYFRAMES);
  260. /**
  261. * Toast can be swiped up or down but
  262. * should start in the middle of the screen.
  263. */
  264. swipeAnimation.progressStart(true, 0.5);
  265. break;
  266. case 'top':
  267. /**
  268. * The bottom edge of the wrapper
  269. * includes the distance between the top
  270. * of the screen and the top of the wrapper
  271. * as well as the wrapper height so the wrapper
  272. * can be dragged fully offscreen.
  273. */
  274. MAX_SWIPE_DISTANCE = wrapperElBox.bottom;
  275. swipeAnimation.keyframes([
  276. { offset: 0, transform: `translateY(${toastPosition.top})` },
  277. { offset: 1, transform: 'translateY(-100%)' },
  278. ]);
  279. swipeAnimation.progressStart(true, 0);
  280. break;
  281. case 'bottom':
  282. default:
  283. /**
  284. * This computes the distance between the
  285. * top of the wrapper and the bottom of the
  286. * screen including the height of the wrapper
  287. * element so it can be dragged fully offscreen.
  288. */
  289. MAX_SWIPE_DISTANCE = hostElHeight - wrapperElBox.top;
  290. swipeAnimation.keyframes([
  291. { offset: 0, transform: `translateY(${toastPosition.bottom})` },
  292. { offset: 1, transform: 'translateY(100%)' },
  293. ]);
  294. swipeAnimation.progressStart(true, 0);
  295. break;
  296. }
  297. const computeStep = (delta) => {
  298. return (delta * INVERSION_FACTOR) / MAX_SWIPE_DISTANCE;
  299. };
  300. const onMove = (detail) => {
  301. const step = STEP_OFFSET + computeStep(detail.deltaY);
  302. swipeAnimation.progressStep(step);
  303. };
  304. const onEnd = (detail) => {
  305. const velocity = detail.velocityY;
  306. const threshold = ((detail.deltaY + velocity * 1000) / MAX_SWIPE_DISTANCE) * INVERSION_FACTOR;
  307. /**
  308. * Disable the gesture for the remainder of the animation.
  309. * It will be re-enabled if the toast animates back to
  310. * its initial presented position.
  311. */
  312. gesture.enable(false);
  313. let shouldDismiss = true;
  314. let playTo = 1;
  315. let step = 0;
  316. let remainingDistance = 0;
  317. if (el.position === 'middle') {
  318. /**
  319. * A middle positioned Toast appears
  320. * in the middle of the screen (at animation offset 0.5).
  321. * As a result, the threshold will be calculated relative
  322. * to this starting position. In other words at animation offset 0.5
  323. * the threshold will be 0. We want the middle Toast to be eligible
  324. * for dismiss when the user has swiped either half way up or down the
  325. * screen. As a result, we divide DISMISS_THRESHOLD in half. We also
  326. * consider when the threshold is a negative in the event the
  327. * user drags up (since the deltaY will also be negative).
  328. */
  329. shouldDismiss = threshold >= DISMISS_THRESHOLD / 2 || threshold <= -DISMISS_THRESHOLD / 2;
  330. /**
  331. * Since we are replacing the keyframes
  332. * below the animation always starts from
  333. * the beginning of the new keyframes.
  334. * Similarly, we are always playing to
  335. * the end of the new keyframes.
  336. */
  337. playTo = 1;
  338. step = 0;
  339. /**
  340. * The Toast should animate from wherever its
  341. * current position is to the desired end state.
  342. *
  343. * To begin, we get the current position of the
  344. * Toast for its starting state.
  345. */
  346. const wrapperElBox = wrapperEl.getBoundingClientRect();
  347. const startOffset = wrapperElBox.top - topPosition;
  348. const startPosition = `${startOffset}px`;
  349. /**
  350. * If the deltaY is negative then the user is swiping
  351. * up, so the Toast should animate to the top of the screen.
  352. * If the deltaY is positive then the user is swiping
  353. * down, so the Toast should animate to the bottom of the screen.
  354. * We also account for when the deltaY is 0, but realistically
  355. * that should never happen because it means the user did not drag
  356. * the toast.
  357. */
  358. const offsetFactor = detail.deltaY <= 0 ? -1 : 1;
  359. const endOffset = (topPosition + wrapperElBox.height) * offsetFactor;
  360. /**
  361. * If the Toast should dismiss
  362. * then we need to figure out which edge of
  363. * the screen it should animate towards.
  364. * By default, the Toast will come
  365. * back to its default state in the
  366. * middle of the screen.
  367. */
  368. const endPosition = shouldDismiss ? `${endOffset}px` : '0px';
  369. const KEYFRAMES = [
  370. { offset: 0, transform: `translateY(${startPosition})` },
  371. { offset: 1, transform: `translateY(${endPosition})` },
  372. ];
  373. swipeAnimation.keyframes(KEYFRAMES);
  374. /**
  375. * Compute the remaining amount of pixels the
  376. * toast needs to move to be fully dismissed.
  377. */
  378. remainingDistance = endOffset - startOffset;
  379. }
  380. else {
  381. shouldDismiss = threshold >= DISMISS_THRESHOLD;
  382. playTo = shouldDismiss ? 1 : 0;
  383. step = computeStep(detail.deltaY);
  384. /**
  385. * Compute the remaining amount of pixels the
  386. * toast needs to move to be fully dismissed.
  387. */
  388. const remainingStepAmount = shouldDismiss ? 1 - step : step;
  389. remainingDistance = remainingStepAmount * MAX_SWIPE_DISTANCE;
  390. }
  391. /**
  392. * The animation speed should depend on how quickly
  393. * the user flicks the toast across the screen. However,
  394. * it should be no slower than 200ms.
  395. * We use Math.abs on the remainingDistance because that value
  396. * can be negative when swiping up on a middle position toast.
  397. */
  398. const duration = Math.min(Math.abs(remainingDistance) / Math.abs(velocity), 200);
  399. swipeAnimation
  400. .onFinish(() => {
  401. if (shouldDismiss) {
  402. onDismiss();
  403. swipeAnimation.destroy();
  404. }
  405. else {
  406. if (el.position === 'middle') {
  407. /**
  408. * If the toast snapped back to
  409. * the middle of the screen we need
  410. * to reset the keyframes
  411. * so the toast can be swiped
  412. * up or down again.
  413. */
  414. swipeAnimation.keyframes(SWIPE_UP_DOWN_KEYFRAMES).progressStart(true, 0.5);
  415. }
  416. else {
  417. swipeAnimation.progressStart(true, 0);
  418. }
  419. /**
  420. * If the toast did not dismiss then
  421. * the user should be able to swipe again.
  422. */
  423. gesture.enable(true);
  424. }
  425. /**
  426. * This must be a one time callback
  427. * otherwise a new callback will
  428. * be added every time onEnd runs.
  429. */
  430. }, { oneTimeCallback: true })
  431. .progressEnd(playTo, step, duration);
  432. };
  433. const gesture = index$2.createGesture({
  434. el: wrapperEl,
  435. gestureName: 'toast-swipe-to-dismiss',
  436. gesturePriority: overlays.OVERLAY_GESTURE_PRIORITY,
  437. /**
  438. * Toast only supports vertical swipes.
  439. * This needs to be updated if we later
  440. * support horizontal swipes.
  441. */
  442. direction: 'y',
  443. onMove,
  444. onEnd,
  445. });
  446. return gesture;
  447. };
  448. const toastIosCss = ":host{--border-width:0;--border-style:none;--border-color:initial;--box-shadow:none;--min-width:auto;--width:auto;--min-height:auto;--height:auto;--max-height:auto;--white-space:normal;top:0;display:block;position:absolute;width:100%;height:100%;outline:none;color:var(--color);font-family:var(--ion-font-family, inherit);contain:strict;z-index:1001;pointer-events:none}:host{inset-inline-start:0}:host(.overlay-hidden){display:none}:host(.ion-color){--button-color:inherit;color:var(--ion-color-contrast)}:host(.ion-color) .toast-button-cancel{color:inherit}:host(.ion-color) .toast-wrapper{background:var(--ion-color-base)}.toast-wrapper{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);pointer-events:auto}.toast-wrapper{inset-inline-start:var(--start);inset-inline-end:var(--end)}.toast-wrapper.toast-top{-webkit-transform:translate3d(0, -100%, 0);transform:translate3d(0, -100%, 0);top:0}.toast-wrapper.toast-bottom{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0);bottom:0}.toast-container{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;height:inherit;min-height:inherit;max-height:inherit;contain:content}.toast-layout-stacked .toast-container{-ms-flex-wrap:wrap;flex-wrap:wrap}.toast-layout-baseline .toast-content{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center}.toast-icon{-webkit-margin-start:16px;margin-inline-start:16px}.toast-content{min-width:0}.toast-message{-ms-flex:1;flex:1;white-space:var(--white-space)}.toast-button-group{display:-ms-flexbox;display:flex}.toast-layout-stacked .toast-button-group{-ms-flex-pack:end;justify-content:end;width:100%}.toast-button{border:0;outline:none;color:var(--button-color);z-index:0}.toast-icon,.toast-button-icon{font-size:1.4em}.toast-button-inner{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}@media (any-hover: hover){.toast-button:hover{cursor:pointer}}:host{--background:var(--ion-color-step-50, var(--ion-background-color-step-50, #f2f2f2));--border-radius:14px;--button-color:var(--ion-color-primary, #0054e9);--color:var(--ion-color-step-850, var(--ion-text-color-step-150, #262626));--max-width:700px;--max-height:478px;--start:10px;--end:10px;font-size:clamp(14px, 0.875rem, 43.4px)}.toast-wrapper{-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;margin-top:auto;margin-bottom:auto;display:block;position:absolute;z-index:10}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){:host(.toast-translucent) .toast-wrapper{background:rgba(var(--ion-background-color-rgb, 255, 255, 255), 0.8);-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}:host(.ion-color.toast-translucent) .toast-wrapper{background:rgba(var(--ion-color-base-rgb), 0.8)}}.toast-wrapper.toast-middle{opacity:0.01}.toast-content{-webkit-padding-start:15px;padding-inline-start:15px;-webkit-padding-end:15px;padding-inline-end:15px;padding-top:15px;padding-bottom:15px}.toast-header{margin-bottom:2px;font-weight:500}.toast-button{-webkit-padding-start:15px;padding-inline-start:15px;-webkit-padding-end:15px;padding-inline-end:15px;padding-top:10px;padding-bottom:10px;min-height:44px;-webkit-transition:background-color, opacity 100ms linear;transition:background-color, opacity 100ms linear;border:0;background-color:transparent;font-family:var(--ion-font-family);font-size:clamp(17px, 1.0625rem, 21.998px);font-weight:500;overflow:hidden}.toast-button.ion-activated{opacity:0.4}@media (any-hover: hover){.toast-button:hover{opacity:0.6}}";
  449. const IonToastIosStyle0 = toastIosCss;
  450. const toastMdCss = ":host{--border-width:0;--border-style:none;--border-color:initial;--box-shadow:none;--min-width:auto;--width:auto;--min-height:auto;--height:auto;--max-height:auto;--white-space:normal;top:0;display:block;position:absolute;width:100%;height:100%;outline:none;color:var(--color);font-family:var(--ion-font-family, inherit);contain:strict;z-index:1001;pointer-events:none}:host{inset-inline-start:0}:host(.overlay-hidden){display:none}:host(.ion-color){--button-color:inherit;color:var(--ion-color-contrast)}:host(.ion-color) .toast-button-cancel{color:inherit}:host(.ion-color) .toast-wrapper{background:var(--ion-color-base)}.toast-wrapper{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);pointer-events:auto}.toast-wrapper{inset-inline-start:var(--start);inset-inline-end:var(--end)}.toast-wrapper.toast-top{-webkit-transform:translate3d(0, -100%, 0);transform:translate3d(0, -100%, 0);top:0}.toast-wrapper.toast-bottom{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0);bottom:0}.toast-container{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;height:inherit;min-height:inherit;max-height:inherit;contain:content}.toast-layout-stacked .toast-container{-ms-flex-wrap:wrap;flex-wrap:wrap}.toast-layout-baseline .toast-content{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center}.toast-icon{-webkit-margin-start:16px;margin-inline-start:16px}.toast-content{min-width:0}.toast-message{-ms-flex:1;flex:1;white-space:var(--white-space)}.toast-button-group{display:-ms-flexbox;display:flex}.toast-layout-stacked .toast-button-group{-ms-flex-pack:end;justify-content:end;width:100%}.toast-button{border:0;outline:none;color:var(--button-color);z-index:0}.toast-icon,.toast-button-icon{font-size:1.4em}.toast-button-inner{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}@media (any-hover: hover){.toast-button:hover{cursor:pointer}}:host{--background:var(--ion-color-step-800, var(--ion-background-color-step-800, #333333));--border-radius:4px;--box-shadow:0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);--button-color:var(--ion-color-primary, #0054e9);--color:var(--ion-color-step-50, var(--ion-text-color-step-950, #f2f2f2));--max-width:700px;--start:8px;--end:8px;font-size:0.875rem}.toast-wrapper{-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;margin-top:auto;margin-bottom:auto;display:block;position:absolute;opacity:0.01;z-index:10}.toast-content{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:14px;padding-bottom:14px}.toast-header{margin-bottom:2px;font-weight:500;line-height:1.25rem}.toast-message{line-height:1.25rem}.toast-layout-baseline .toast-button-group-start{-webkit-margin-start:8px;margin-inline-start:8px}.toast-layout-stacked .toast-button-group-start{-webkit-margin-end:8px;margin-inline-end:8px;margin-top:8px}.toast-layout-baseline .toast-button-group-end{-webkit-margin-end:8px;margin-inline-end:8px}.toast-layout-stacked .toast-button-group-end{-webkit-margin-end:8px;margin-inline-end:8px;margin-bottom:8px}.toast-button{-webkit-padding-start:15px;padding-inline-start:15px;-webkit-padding-end:15px;padding-inline-end:15px;padding-top:10px;padding-bottom:10px;position:relative;background-color:transparent;font-family:var(--ion-font-family);font-size:0.875rem;font-weight:500;letter-spacing:0.84px;text-transform:uppercase;overflow:hidden}.toast-button-cancel{color:var(--ion-color-step-100, var(--ion-text-color-step-900, #e6e6e6))}.toast-button-icon-only{border-radius:50%;-webkit-padding-start:9px;padding-inline-start:9px;-webkit-padding-end:9px;padding-inline-end:9px;padding-top:9px;padding-bottom:9px;width:36px;height:36px}@media (any-hover: hover){.toast-button:hover{background-color:rgba(var(--ion-color-primary-rgb, 0, 84, 233), 0.08)}.toast-button-cancel:hover{background-color:rgba(var(--ion-background-color-rgb, 255, 255, 255), 0.08)}}";
  451. const IonToastMdStyle0 = toastMdCss;
  452. const Toast = class {
  453. constructor(hostRef) {
  454. index$3.registerInstance(this, hostRef);
  455. this.didPresent = index$3.createEvent(this, "ionToastDidPresent", 7);
  456. this.willPresent = index$3.createEvent(this, "ionToastWillPresent", 7);
  457. this.willDismiss = index$3.createEvent(this, "ionToastWillDismiss", 7);
  458. this.didDismiss = index$3.createEvent(this, "ionToastDidDismiss", 7);
  459. this.didPresentShorthand = index$3.createEvent(this, "didPresent", 7);
  460. this.willPresentShorthand = index$3.createEvent(this, "willPresent", 7);
  461. this.willDismissShorthand = index$3.createEvent(this, "willDismiss", 7);
  462. this.didDismissShorthand = index$3.createEvent(this, "didDismiss", 7);
  463. this.delegateController = overlays.createDelegateController(this);
  464. this.lockController = lockController.createLockController();
  465. this.triggerController = overlays.createTriggerController();
  466. this.customHTMLEnabled = index$1.config.get('innerHTMLTemplatesEnabled', config.ENABLE_HTML_CONTENT_DEFAULT);
  467. this.presented = false;
  468. this.dispatchCancelHandler = (ev) => {
  469. const role = ev.detail.role;
  470. if (overlays.isCancel(role)) {
  471. const cancelButton = this.getButtons().find((b) => b.role === 'cancel');
  472. this.callButtonHandler(cancelButton);
  473. }
  474. };
  475. /**
  476. * Create a new swipe gesture so Toast
  477. * can be swiped to dismiss.
  478. */
  479. this.createSwipeGesture = (toastPosition) => {
  480. const gesture = (this.gesture = createSwipeToDismissGesture(this.el, toastPosition, () => {
  481. /**
  482. * If the gesture completed then
  483. * we should dismiss the toast.
  484. */
  485. this.dismiss(undefined, overlays.GESTURE);
  486. }));
  487. gesture.enable(true);
  488. };
  489. /**
  490. * Destroy an existing swipe gesture
  491. * so Toast can no longer be swiped to dismiss.
  492. */
  493. this.destroySwipeGesture = () => {
  494. const { gesture } = this;
  495. if (gesture === undefined) {
  496. return;
  497. }
  498. gesture.destroy();
  499. this.gesture = undefined;
  500. };
  501. /**
  502. * Returns `true` if swipeGesture
  503. * is configured to a value that enables the swipe behavior.
  504. * Returns `false` otherwise.
  505. */
  506. this.prefersSwipeGesture = () => {
  507. const { swipeGesture } = this;
  508. return swipeGesture === 'vertical';
  509. };
  510. this.revealContentToScreenReader = false;
  511. this.overlayIndex = undefined;
  512. this.delegate = undefined;
  513. this.hasController = false;
  514. this.color = undefined;
  515. this.enterAnimation = undefined;
  516. this.leaveAnimation = undefined;
  517. this.cssClass = undefined;
  518. this.duration = index$1.config.getNumber('toastDuration', 0);
  519. this.header = undefined;
  520. this.layout = 'baseline';
  521. this.message = undefined;
  522. this.keyboardClose = false;
  523. this.position = 'bottom';
  524. this.positionAnchor = undefined;
  525. this.buttons = undefined;
  526. this.translucent = false;
  527. this.animated = true;
  528. this.icon = undefined;
  529. this.htmlAttributes = undefined;
  530. this.swipeGesture = undefined;
  531. this.isOpen = false;
  532. this.trigger = undefined;
  533. }
  534. swipeGestureChanged() {
  535. /**
  536. * If the Toast is presented, then we need to destroy
  537. * any actives gestures before a new gesture is potentially
  538. * created below.
  539. *
  540. * If the Toast is dismissed, then no gesture should be available
  541. * since the Toast is not visible. This case should never
  542. * happen since the "dismiss" method handles destroying
  543. * any active swipe gestures, but we keep this code
  544. * around to handle the first case.
  545. */
  546. this.destroySwipeGesture();
  547. /**
  548. * A new swipe gesture should only be created
  549. * if the Toast is presented. If the Toast is not
  550. * yet presented then the "present" method will
  551. * handle calling the swipe gesture setup function.
  552. */
  553. if (this.presented && this.prefersSwipeGesture()) {
  554. /**
  555. * If the Toast is presented then
  556. * lastPresentedPosition is defined.
  557. */
  558. this.createSwipeGesture(this.lastPresentedPosition);
  559. }
  560. }
  561. onIsOpenChange(newValue, oldValue) {
  562. if (newValue === true && oldValue === false) {
  563. this.present();
  564. }
  565. else if (newValue === false && oldValue === true) {
  566. this.dismiss();
  567. }
  568. }
  569. triggerChanged() {
  570. const { trigger, el, triggerController } = this;
  571. if (trigger) {
  572. triggerController.addClickListener(el, trigger);
  573. }
  574. }
  575. connectedCallback() {
  576. overlays.prepareOverlay(this.el);
  577. this.triggerChanged();
  578. }
  579. disconnectedCallback() {
  580. this.triggerController.removeClickListener();
  581. }
  582. componentWillLoad() {
  583. var _a;
  584. if (!((_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id)) {
  585. overlays.setOverlayId(this.el);
  586. }
  587. }
  588. componentDidLoad() {
  589. /**
  590. * If toast was rendered with isOpen="true"
  591. * then we should open toast immediately.
  592. */
  593. if (this.isOpen === true) {
  594. helpers.raf(() => this.present());
  595. }
  596. /**
  597. * When binding values in frameworks such as Angular
  598. * it is possible for the value to be set after the Web Component
  599. * initializes but before the value watcher is set up in Stencil.
  600. * As a result, the watcher callback may not be fired.
  601. * We work around this by manually calling the watcher
  602. * callback when the component has loaded and the watcher
  603. * is configured.
  604. */
  605. this.triggerChanged();
  606. }
  607. /**
  608. * Present the toast overlay after it has been created.
  609. */
  610. async present() {
  611. const unlock = await this.lockController.lock();
  612. await this.delegateController.attachViewToDom();
  613. const { el, position } = this;
  614. const anchor = this.getAnchorElement();
  615. const animationPosition = getAnimationPosition(position, anchor, ionicGlobal.getIonMode(this), el);
  616. /**
  617. * Cache the calculated position of the toast, so we can re-use it
  618. * in the dismiss animation.
  619. */
  620. this.lastPresentedPosition = animationPosition;
  621. await overlays.present(this, 'toastEnter', iosEnterAnimation, mdEnterAnimation, {
  622. position,
  623. top: animationPosition.top,
  624. bottom: animationPosition.bottom,
  625. });
  626. /**
  627. * Content is revealed to screen readers after
  628. * the transition to avoid jank since this
  629. * state updates will cause a re-render.
  630. */
  631. this.revealContentToScreenReader = true;
  632. if (this.duration > 0) {
  633. this.durationTimeout = setTimeout(() => this.dismiss(undefined, 'timeout'), this.duration);
  634. }
  635. /**
  636. * If the Toast has a swipe gesture then we can
  637. * create the gesture so users can swipe the
  638. * presented Toast.
  639. */
  640. if (this.prefersSwipeGesture()) {
  641. this.createSwipeGesture(animationPosition);
  642. }
  643. unlock();
  644. }
  645. /**
  646. * Dismiss the toast overlay after it has been presented.
  647. *
  648. * @param data Any data to emit in the dismiss events.
  649. * @param role The role of the element that is dismissing the toast.
  650. * This can be useful in a button handler for determining which button was
  651. * clicked to dismiss the toast.
  652. * Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
  653. *
  654. * This is a no-op if the overlay has not been presented yet. If you want
  655. * to remove an overlay from the DOM that was never presented, use the
  656. * [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
  657. */
  658. async dismiss(data, role) {
  659. var _a, _b;
  660. const unlock = await this.lockController.lock();
  661. const { durationTimeout, position, lastPresentedPosition } = this;
  662. if (durationTimeout) {
  663. clearTimeout(durationTimeout);
  664. }
  665. const dismissed = await overlays.dismiss(this, data, role, 'toastLeave', iosLeaveAnimation, mdLeaveAnimation,
  666. /**
  667. * Fetch the cached position that was calculated back in the present
  668. * animation. We always want to animate the dismiss from the same
  669. * position the present stopped at, so the animation looks continuous.
  670. */
  671. {
  672. position,
  673. top: (_a = lastPresentedPosition === null || lastPresentedPosition === void 0 ? void 0 : lastPresentedPosition.top) !== null && _a !== void 0 ? _a : '',
  674. bottom: (_b = lastPresentedPosition === null || lastPresentedPosition === void 0 ? void 0 : lastPresentedPosition.bottom) !== null && _b !== void 0 ? _b : '',
  675. });
  676. if (dismissed) {
  677. this.delegateController.removeViewFromDom();
  678. this.revealContentToScreenReader = false;
  679. }
  680. this.lastPresentedPosition = undefined;
  681. /**
  682. * If the Toast has a swipe gesture then we can
  683. * safely destroy it now that it is dismissed.
  684. */
  685. this.destroySwipeGesture();
  686. unlock();
  687. return dismissed;
  688. }
  689. /**
  690. * Returns a promise that resolves when the toast did dismiss.
  691. */
  692. onDidDismiss() {
  693. return overlays.eventMethod(this.el, 'ionToastDidDismiss');
  694. }
  695. /**
  696. * Returns a promise that resolves when the toast will dismiss.
  697. */
  698. onWillDismiss() {
  699. return overlays.eventMethod(this.el, 'ionToastWillDismiss');
  700. }
  701. getButtons() {
  702. const buttons = this.buttons
  703. ? this.buttons.map((b) => {
  704. return typeof b === 'string' ? { text: b } : b;
  705. })
  706. : [];
  707. return buttons;
  708. }
  709. /**
  710. * Returns the element specified by the positionAnchor prop,
  711. * or undefined if prop's value is an ID string and the element
  712. * is not found in the DOM.
  713. */
  714. getAnchorElement() {
  715. const { position, positionAnchor, el } = this;
  716. /**
  717. * If positionAnchor is undefined then
  718. * no anchor should be used when presenting the toast.
  719. */
  720. if (positionAnchor === undefined) {
  721. return;
  722. }
  723. if (position === 'middle' && positionAnchor !== undefined) {
  724. index$1.printIonWarning('[ion-toast] - The positionAnchor property is ignored when using position="middle".', this.el);
  725. return undefined;
  726. }
  727. if (typeof positionAnchor === 'string') {
  728. /**
  729. * If the anchor is defined as an ID, find the element.
  730. * We do this on every present so the toast doesn't need
  731. * to account for the surrounding DOM changing since the
  732. * last time it was presented.
  733. */
  734. const foundEl = document.getElementById(positionAnchor);
  735. if (foundEl === null) {
  736. index$1.printIonWarning(`[ion-toast] - An anchor element with an ID of "${positionAnchor}" was not found in the DOM.`, el);
  737. return undefined;
  738. }
  739. return foundEl;
  740. }
  741. if (positionAnchor instanceof HTMLElement) {
  742. return positionAnchor;
  743. }
  744. index$1.printIonWarning('[ion-toast] - Invalid positionAnchor value:', positionAnchor, el);
  745. return undefined;
  746. }
  747. async buttonClick(button) {
  748. const role = button.role;
  749. if (overlays.isCancel(role)) {
  750. return this.dismiss(undefined, role);
  751. }
  752. const shouldDismiss = await this.callButtonHandler(button);
  753. if (shouldDismiss) {
  754. return this.dismiss(undefined, role);
  755. }
  756. return Promise.resolve();
  757. }
  758. async callButtonHandler(button) {
  759. if (button === null || button === void 0 ? void 0 : button.handler) {
  760. // a handler has been provided, execute it
  761. // pass the handler the values from the inputs
  762. try {
  763. const rtn = await overlays.safeCall(button.handler);
  764. if (rtn === false) {
  765. // if the return value of the handler is false then do not dismiss
  766. return false;
  767. }
  768. }
  769. catch (e) {
  770. index$1.printIonError('[ion-toast] - Exception in callButtonHandler:', e);
  771. }
  772. }
  773. return true;
  774. }
  775. renderButtons(buttons, side) {
  776. if (buttons.length === 0) {
  777. return;
  778. }
  779. const mode = ionicGlobal.getIonMode(this);
  780. const buttonGroupsClasses = {
  781. 'toast-button-group': true,
  782. [`toast-button-group-${side}`]: true,
  783. };
  784. return (index$3.h("div", { class: buttonGroupsClasses }, buttons.map((b) => (index$3.h("button", Object.assign({}, b.htmlAttributes, { type: "button", class: buttonClass(b), tabIndex: 0, onClick: () => this.buttonClick(b), part: buttonPart(b) }), index$3.h("div", { class: "toast-button-inner" }, b.icon && (index$3.h("ion-icon", { "aria-hidden": "true", icon: b.icon, slot: b.text === undefined ? 'icon-only' : undefined, class: "toast-button-icon" })), b.text), mode === 'md' && (index$3.h("ion-ripple-effect", { type: b.icon !== undefined && b.text === undefined ? 'unbounded' : 'bounded' })))))));
  785. }
  786. /**
  787. * Render the `message` property.
  788. * @param key - A key to give the element a stable identity. This is used to improve compatibility with screen readers.
  789. * @param ariaHidden - If "true" then content will be hidden from screen readers.
  790. */
  791. renderToastMessage(key, ariaHidden = null) {
  792. const { customHTMLEnabled, message } = this;
  793. if (customHTMLEnabled) {
  794. return (index$3.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message", innerHTML: config.sanitizeDOMString(message) }));
  795. }
  796. return (index$3.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message" }, message));
  797. }
  798. /**
  799. * Render the `header` property.
  800. * @param key - A key to give the element a stable identity. This is used to improve compatibility with screen readers.
  801. * @param ariaHidden - If "true" then content will be hidden from screen readers.
  802. */
  803. renderHeader(key, ariaHidden = null) {
  804. return (index$3.h("div", { key: key, class: "toast-header", "aria-hidden": ariaHidden, part: "header" }, this.header));
  805. }
  806. render() {
  807. const { layout, el, revealContentToScreenReader, header, message } = this;
  808. const allButtons = this.getButtons();
  809. const startButtons = allButtons.filter((b) => b.side === 'start');
  810. const endButtons = allButtons.filter((b) => b.side !== 'start');
  811. const mode = ionicGlobal.getIonMode(this);
  812. const wrapperClass = {
  813. 'toast-wrapper': true,
  814. [`toast-${this.position}`]: true,
  815. [`toast-layout-${layout}`]: true,
  816. };
  817. /**
  818. * Stacked buttons are only meant to be
  819. * used with one type of button.
  820. */
  821. if (layout === 'stacked' && startButtons.length > 0 && endButtons.length > 0) {
  822. index$1.printIonWarning('[ion-toast] - This toast is using start and end buttons with the stacked toast layout. We recommend following the best practice of using either start or end buttons with the stacked toast layout.', el);
  823. }
  824. return (index$3.h(index$3.Host, Object.assign({ key: 'a2216d860255c99337464370dcb12f6125871a50', tabindex: "-1" }, this.htmlAttributes, { style: {
  825. zIndex: `${60000 + this.overlayIndex}`,
  826. }, class: theme.createColorClasses(this.color, Object.assign(Object.assign({ [mode]: true }, theme.getClassMap(this.cssClass)), { 'overlay-hidden': true, 'toast-translucent': this.translucent })), onIonToastWillDismiss: this.dispatchCancelHandler }), index$3.h("div", { key: 'd5adf8bc4c6c52431600033a76c4795689f9b412', class: wrapperClass }, index$3.h("div", { key: 'ab694497ae37ceba123217eb48800129b9bebb84', class: "toast-container", part: "container" }, this.renderButtons(startButtons, 'start'), this.icon !== undefined && (index$3.h("ion-icon", { key: '224854fa3989ce0eb69416cb5b0cc55fc9f131ea', class: "toast-icon", part: "icon", icon: this.icon, lazy: false, "aria-hidden": "true" })), index$3.h("div", { key: 'c8e11fb5bdac202987f5c8613a0ebbd42bda946e', class: "toast-content", role: "status", "aria-atomic": "true", "aria-live": "polite" }, !revealContentToScreenReader && header !== undefined && this.renderHeader('oldHeader', 'true'), !revealContentToScreenReader && message !== undefined && this.renderToastMessage('oldMessage', 'true'), revealContentToScreenReader && header !== undefined && this.renderHeader('header'), revealContentToScreenReader && message !== undefined && this.renderToastMessage('header')), this.renderButtons(endButtons, 'end')))));
  827. }
  828. get el() { return index$3.getElement(this); }
  829. static get watchers() { return {
  830. "swipeGesture": ["swipeGestureChanged"],
  831. "isOpen": ["onIsOpenChange"],
  832. "trigger": ["triggerChanged"]
  833. }; }
  834. };
  835. const buttonClass = (button) => {
  836. return {
  837. 'toast-button': true,
  838. 'toast-button-icon-only': button.icon !== undefined && button.text === undefined,
  839. [`toast-button-${button.role}`]: button.role !== undefined,
  840. 'ion-focusable': true,
  841. 'ion-activatable': true,
  842. };
  843. };
  844. const buttonPart = (button) => {
  845. return overlays.isCancel(button.role) ? 'button cancel' : 'button';
  846. };
  847. Toast.style = {
  848. ios: IonToastIosStyle0,
  849. md: IonToastMdStyle0
  850. };
  851. exports.ion_toast = Toast;