@ionic/core 7.5.4-dev.11699632730.1e6d9b70 → 7.5.4-dev.11699895923.130c21aa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/components/ion-modal.js +2 -2
  2. package/components/ion-toast.js +417 -67
  3. package/components/overlays.js +2 -1
  4. package/css/ionic.bundle.css +1 -1
  5. package/css/ionic.bundle.css.map +1 -1
  6. package/css/utils.bundle.css +0 -133
  7. package/css/utils.bundle.css.map +1 -1
  8. package/dist/cjs/index.cjs.js +1 -1
  9. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  10. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  11. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  12. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  13. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  14. package/dist/cjs/ion-modal.cjs.entry.js +2 -2
  15. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  16. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  17. package/dist/cjs/ion-toast.cjs.entry.js +430 -83
  18. package/dist/cjs/ionic.cjs.js +1 -1
  19. package/dist/cjs/loader.cjs.js +1 -1
  20. package/dist/cjs/{overlays-d80a2cf2.js → overlays-ba1a1e21.js} +2 -0
  21. package/dist/collection/components/modal/gestures/swipe-to-close.js +2 -1
  22. package/dist/collection/components/toast/animations/ios.enter.js +2 -1
  23. package/dist/collection/components/toast/animations/md.enter.js +2 -1
  24. package/dist/collection/components/toast/animations/utils.js +11 -0
  25. package/dist/collection/components/toast/gestures/swipe-to-dismiss.js +267 -0
  26. package/dist/collection/components/toast/test/swipe-gesture/toast.e2e.js +84 -0
  27. package/dist/collection/components/toast/toast.js +104 -1
  28. package/dist/collection/utils/overlays.js +1 -0
  29. package/dist/docs.json +37 -1
  30. package/dist/esm/index.js +1 -1
  31. package/dist/esm/ion-action-sheet.entry.js +1 -1
  32. package/dist/esm/ion-alert.entry.js +1 -1
  33. package/dist/esm/ion-datetime_3.entry.js +1 -1
  34. package/dist/esm/ion-loading.entry.js +1 -1
  35. package/dist/esm/ion-menu_3.entry.js +1 -1
  36. package/dist/esm/ion-modal.entry.js +2 -2
  37. package/dist/esm/ion-popover.entry.js +1 -1
  38. package/dist/esm/ion-select_3.entry.js +1 -1
  39. package/dist/esm/ion-toast.entry.js +413 -66
  40. package/dist/esm/ionic.js +1 -1
  41. package/dist/esm/loader.js +1 -1
  42. package/dist/esm/{overlays-a31c8878.js → overlays-d79b34b4.js} +2 -1
  43. package/dist/esm-es5/index.js +1 -1
  44. package/dist/esm-es5/ion-action-sheet.entry.js +1 -1
  45. package/dist/esm-es5/ion-alert.entry.js +1 -1
  46. package/dist/esm-es5/ion-datetime_3.entry.js +1 -1
  47. package/dist/esm-es5/ion-loading.entry.js +1 -1
  48. package/dist/esm-es5/ion-menu_3.entry.js +1 -1
  49. package/dist/esm-es5/ion-modal.entry.js +1 -1
  50. package/dist/esm-es5/ion-popover.entry.js +1 -1
  51. package/dist/esm-es5/ion-select_3.entry.js +1 -1
  52. package/dist/esm-es5/ion-toast.entry.js +1 -1
  53. package/dist/esm-es5/ionic.js +1 -1
  54. package/dist/esm-es5/loader.js +1 -1
  55. package/dist/esm-es5/{overlays-a31c8878.js → overlays-d79b34b4.js} +1 -1
  56. package/dist/html.html-data.json +9 -0
  57. package/dist/ionic/index.esm.js +1 -1
  58. package/dist/ionic/ionic.esm.js +1 -1
  59. package/dist/ionic/{p-c32a6c04.entry.js → p-020dd47f.entry.js} +1 -1
  60. package/dist/ionic/p-02663394.system.js +1 -1
  61. package/dist/ionic/{p-b86c6c05.entry.js → p-138a4a93.entry.js} +1 -1
  62. package/dist/ionic/{p-4c75cb48.system.js → p-1511dc35.system.js} +1 -1
  63. package/dist/ionic/{p-3a769110.system.entry.js → p-1fc6b5a5.system.entry.js} +1 -1
  64. package/dist/ionic/{p-90020ef0.system.entry.js → p-2151a4a4.system.entry.js} +1 -1
  65. package/dist/ionic/{p-539c09e8.system.entry.js → p-2ae9a434.system.entry.js} +1 -1
  66. package/dist/ionic/{p-1e59dd04.system.entry.js → p-3dedd258.system.entry.js} +1 -1
  67. package/dist/ionic/{p-98c70e3c.system.js → p-53f75103.system.js} +1 -1
  68. package/dist/ionic/{p-75338011.entry.js → p-664cc492.entry.js} +1 -1
  69. package/dist/ionic/p-6c89025e.system.entry.js +4 -0
  70. package/dist/ionic/{p-8140a084.entry.js → p-78a8e1e8.entry.js} +1 -1
  71. package/dist/ionic/{p-d54a8393.system.entry.js → p-81bdfa1c.system.entry.js} +1 -1
  72. package/dist/ionic/p-8414ed31.entry.js +4 -0
  73. package/dist/ionic/{p-307963db.js → p-938c4956.js} +1 -1
  74. package/dist/ionic/{p-c09fa14d.entry.js → p-a9424586.entry.js} +1 -1
  75. package/dist/ionic/{p-59107f99.system.entry.js → p-ab1908ba.system.entry.js} +1 -1
  76. package/dist/ionic/{p-e91c7eb4.system.entry.js → p-cb6c380e.system.entry.js} +1 -1
  77. package/dist/ionic/{p-a4e4dba1.entry.js → p-e119ac8b.entry.js} +1 -1
  78. package/dist/ionic/p-e3b7a2d0.system.entry.js +4 -0
  79. package/dist/ionic/{p-53b1930e.entry.js → p-e525dbea.entry.js} +1 -1
  80. package/dist/ionic/p-f2d4e3c8.entry.js +4 -0
  81. package/dist/types/components/toast/animations/utils.d.ts +9 -0
  82. package/dist/types/components/toast/gestures/swipe-to-dismiss.d.ts +9 -0
  83. package/dist/types/components/toast/toast-interface.d.ts +1 -0
  84. package/dist/types/components/toast/toast.d.ts +28 -1
  85. package/dist/types/components.d.ts +10 -2
  86. package/dist/types/utils/overlays.d.ts +1 -0
  87. package/hydrate/index.js +413 -66
  88. package/package.json +1 -1
  89. package/css/themes/dark.css +0 -135
  90. package/css/themes/dark.css.map +0 -1
  91. package/dist/ionic/p-139bd79d.system.entry.js +0 -4
  92. package/dist/ionic/p-7d68e4ea.entry.js +0 -4
  93. package/dist/ionic/p-999a7271.entry.js +0 -4
  94. package/dist/ionic/p-e66f7d10.system.entry.js +0 -4
@@ -8,7 +8,7 @@ import { g as getElementRoot, m as clamp, r as raf, d as inheritAttributes, l as
8
8
  import { c as createLockController } from './lock-controller.js';
9
9
  import { p as printIonWarning } from './index6.js';
10
10
  import { g as getCapacitor } from './capacitor.js';
11
- import { G as GESTURE, e as createTriggerController, B as BACKDROP, j as prepareOverlay, k as setOverlayId, f as present, q as activeAnimations, g as dismiss, h as eventMethod } from './overlays.js';
11
+ import { G as GESTURE, O as OVERLAY_GESTURE_PRIORITY, e as createTriggerController, B as BACKDROP, j as prepareOverlay, k as setOverlayId, f as present, q as activeAnimations, g as dismiss, h as eventMethod } from './overlays.js';
12
12
  import { g as getClassMap } from './theme.js';
13
13
  import { e as deepReady, w as waitForMount } from './index2.js';
14
14
  import { b as getIonMode, c as config } from './ionic-global.js';
@@ -502,7 +502,7 @@ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) =>
502
502
  const gesture = createGesture({
503
503
  el,
504
504
  gestureName: 'modalSwipeToClose',
505
- gesturePriority: 39,
505
+ gesturePriority: OVERLAY_GESTURE_PRIORITY,
506
506
  direction: 'y',
507
507
  threshold: 10,
508
508
  canStart,
@@ -1,19 +1,107 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
4
+ import { Build, proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
5
5
  import { E as ENABLE_HTML_CONTENT_DEFAULT, a as sanitizeDOMString } from './config.js';
6
6
  import { g as getElementRoot, r as raf } from './helpers.js';
7
7
  import { c as createLockController } from './lock-controller.js';
8
8
  import { p as printIonWarning } from './index6.js';
9
- import { d as createDelegateController, e as createTriggerController, i as isCancel, j as prepareOverlay, k as setOverlayId, f as present, g as dismiss, h as eventMethod, s as safeCall } from './overlays.js';
9
+ import { O as OVERLAY_GESTURE_PRIORITY, d as createDelegateController, e as createTriggerController, i as isCancel, j as prepareOverlay, k as setOverlayId, f as present, g as dismiss, h as eventMethod, s as safeCall, G as GESTURE } from './overlays.js';
10
10
  import { g as getClassMap, c as createColorClasses } from './theme.js';
11
11
  import { c as config, b as getIonMode } from './ionic-global.js';
12
12
  import { c as createAnimation } from './animation.js';
13
13
  import { w as win } from './index5.js';
14
+ import { createGesture } from './index3.js';
14
15
  import { d as defineCustomElement$3 } from './icon.js';
15
16
  import { d as defineCustomElement$2 } from './ripple-effect.js';
16
17
 
18
+ /**
19
+ * Calculate the CSS top and bottom position of the toast, to be used
20
+ * as starting points for the animation keyframes.
21
+ *
22
+ * The default animations for both MD and iOS
23
+ * use translateY, which calculates from the
24
+ * top edge of the screen. This behavior impacts
25
+ * how we compute the offset when a toast has
26
+ * position='bottom' since we need to calculate from
27
+ * the bottom edge of the screen instead.
28
+ *
29
+ * @param position The value of the toast's position prop.
30
+ * @param positionAnchor The element the toast should be anchored to,
31
+ * if applicable.
32
+ * @param mode The toast component's mode (md, ios, etc).
33
+ * @param toast A reference to the toast element itself.
34
+ */
35
+ function getAnimationPosition(position, positionAnchor, mode, toast) {
36
+ /**
37
+ * Start with a predefined offset from the edge the toast will be
38
+ * positioned relative to, whether on the screen or anchor element.
39
+ */
40
+ let offset;
41
+ if (mode === 'md') {
42
+ offset = position === 'top' ? 8 : -8;
43
+ }
44
+ else {
45
+ offset = position === 'top' ? 10 : -10;
46
+ }
47
+ /**
48
+ * If positionAnchor is defined, add in the distance from the target
49
+ * screen edge to the target anchor edge. For position="top", the
50
+ * bottom anchor edge is targeted. For position="bottom", the top
51
+ * anchor edge is targeted.
52
+ */
53
+ if (positionAnchor && win) {
54
+ warnIfAnchorIsHidden(positionAnchor, toast);
55
+ const box = positionAnchor.getBoundingClientRect();
56
+ if (position === 'top') {
57
+ offset += box.bottom;
58
+ }
59
+ else if (position === 'bottom') {
60
+ /**
61
+ * Just box.top is the distance from the top edge of the screen
62
+ * to the top edge of the anchor. We want to calculate from the
63
+ * bottom edge of the screen instead.
64
+ */
65
+ offset -= win.innerHeight - box.top;
66
+ }
67
+ /**
68
+ * We don't include safe area here because that should already be
69
+ * accounted for when checking the position of the anchor.
70
+ */
71
+ return {
72
+ top: `${offset}px`,
73
+ bottom: `${offset}px`,
74
+ };
75
+ }
76
+ else {
77
+ return {
78
+ top: `calc(${offset}px + var(--ion-safe-area-top, 0px))`,
79
+ bottom: `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`,
80
+ };
81
+ }
82
+ }
83
+ /**
84
+ * If the anchor element is hidden, getBoundingClientRect()
85
+ * will return all 0s for it, which can cause unexpected
86
+ * results in the position calculation when animating.
87
+ */
88
+ function warnIfAnchorIsHidden(positionAnchor, toast) {
89
+ if (positionAnchor.offsetParent === null) {
90
+ printIonWarning('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);
91
+ }
92
+ }
93
+ /**
94
+ * Returns the top offset required to place
95
+ * the toast in the middle of the screen.
96
+ * Only needed when position="toast".
97
+ * @param toastHeight - The height of the ion-toast element
98
+ * @param wrapperHeight - The height of the .toast-wrapper element
99
+ * inside the toast's shadow root.
100
+ */
101
+ const getOffsetForMiddlePosition = (toastHeight, wrapperHeight) => {
102
+ return Math.floor(toastHeight / 2 - wrapperHeight / 2);
103
+ };
104
+
17
105
  /**
18
106
  * iOS Toast Enter Animation
19
107
  */
@@ -29,7 +117,7 @@ const iosEnterAnimation = (baseEl, opts) => {
29
117
  wrapperAnimation.fromTo('transform', 'translateY(-100%)', `translateY(${top})`);
30
118
  break;
31
119
  case 'middle':
32
- const topPosition = Math.floor(baseEl.clientHeight / 2 - wrapperEl.clientHeight / 2);
120
+ const topPosition = getOffsetForMiddlePosition(baseEl.clientHeight, wrapperEl.clientHeight);
33
121
  wrapperEl.style.top = `${topPosition}px`;
34
122
  wrapperAnimation.fromTo('opacity', 0.01, 1);
35
123
  break;
@@ -80,7 +168,7 @@ const mdEnterAnimation = (baseEl, opts) => {
80
168
  wrapperAnimation.fromTo('opacity', 0.01, 1);
81
169
  break;
82
170
  case 'middle':
83
- const topPosition = Math.floor(baseEl.clientHeight / 2 - wrapperEl.clientHeight / 2);
171
+ const topPosition = getOffsetForMiddlePosition(baseEl.clientHeight, wrapperEl.clientHeight);
84
172
  wrapperEl.style.top = `${topPosition}px`;
85
173
  wrapperAnimation.fromTo('opacity', 0.01, 1);
86
174
  break;
@@ -105,80 +193,263 @@ const mdLeaveAnimation = (baseEl) => {
105
193
  };
106
194
 
107
195
  /**
108
- * Calculate the CSS top and bottom position of the toast, to be used
109
- * as starting points for the animation keyframes.
110
- *
111
- * The default animations for both MD and iOS
112
- * use translateY, which calculates from the
113
- * top edge of the screen. This behavior impacts
114
- * how we compute the offset when a toast has
115
- * position='bottom' since we need to calculate from
116
- * the bottom edge of the screen instead.
117
- *
118
- * @param position The value of the toast's position prop.
119
- * @param positionAnchor The element the toast should be anchored to,
120
- * if applicable.
121
- * @param mode The toast component's mode (md, ios, etc).
122
- * @param toast A reference to the toast element itself.
196
+ * Create a gesture that allows the Toast
197
+ * to be swiped to dismiss.
198
+ * @param el - The Toast element
199
+ * @param toastPosition - The last computed position of the Toast. This is computed in the "present" method.
200
+ * @param onDismiss - A callback to fire when the Toast was swiped to dismiss.
123
201
  */
124
- function getAnimationPosition(position, positionAnchor, mode, toast) {
202
+ const createSwipeToDismissGesture = (el, toastPosition, onDismiss) => {
125
203
  /**
126
- * Start with a predefined offset from the edge the toast will be
127
- * positioned relative to, whether on the screen or anchor element.
204
+ * Users should swipe on the visible toast wrapper
205
+ * rather than on ion-toast which covers the entire screen.
206
+ * When testing the class instance the inner wrapper will not
207
+ * be defined. As a result, we use a placeholder element in those environments.
128
208
  */
129
- let offset;
130
- if (mode === 'md') {
131
- offset = position === 'top' ? 8 : -8;
132
- }
133
- else {
134
- offset = position === 'top' ? 10 : -10;
135
- }
209
+ const wrapperEl = Build.isTesting
210
+ ? document.createElement('div')
211
+ : getElementRoot(el).querySelector('.toast-wrapper');
212
+ const hostElHeight = el.clientHeight;
213
+ const wrapperElBox = wrapperEl.getBoundingClientRect();
136
214
  /**
137
- * If positionAnchor is defined, add in the distance from the target
138
- * screen edge to the target anchor edge. For position="top", the
139
- * bottom anchor edge is targeted. For position="bottom", the top
140
- * anchor edge is targeted.
215
+ * The maximum amount that
216
+ * the toast can be swiped. This should
217
+ * account for the wrapper element's height
218
+ * too so the toast can be swiped offscreen
219
+ * completely.
141
220
  */
142
- if (positionAnchor && win) {
143
- warnIfAnchorIsHidden(positionAnchor, toast);
144
- const box = positionAnchor.getBoundingClientRect();
145
- if (position === 'top') {
146
- offset += box.bottom;
221
+ let MAX_SWIPE_DISTANCE = 0;
222
+ /**
223
+ * The step value at which a toast
224
+ * is eligible for dismissing via gesture.
225
+ */
226
+ const DISMISS_THRESHOLD = 0.5;
227
+ /**
228
+ * The middle position Toast starts 50% of the way
229
+ * through the animation, so we need to use this
230
+ * as the starting point for our step values.
231
+ */
232
+ const STEP_OFFSET = el.position === 'middle' ? 0.5 : 0;
233
+ /**
234
+ * When the Toast is at the top users will be
235
+ * swiping up. As a result, the delta values will be
236
+ * negative numbers which will result in negative steps
237
+ * and thresholds. As a result, we need to make those numbers
238
+ * positive.
239
+ */
240
+ const INVERSION_FACTOR = el.position === 'top' ? -1 : 1;
241
+ /**
242
+ * The top offset that places the
243
+ * toast in the middle of the screen.
244
+ * Only needed when position="middle".
245
+ */
246
+ const topPosition = getOffsetForMiddlePosition(hostElHeight, wrapperElBox.height);
247
+ const SWIPE_UP_DOWN_KEYFRAMES = [
248
+ { offset: 0, transform: `translateY(-${topPosition + wrapperElBox.height}px)` },
249
+ { offset: 0.5, transform: `translateY(0px)` },
250
+ { offset: 1, transform: `translateY(${topPosition + wrapperElBox.height}px)` },
251
+ ];
252
+ const swipeAnimation = createAnimation('toast-swipe-to-dismiss-animation')
253
+ .addElement(wrapperEl)
254
+ /**
255
+ * The specific value here does not actually
256
+ * matter. We just need this to be a positive
257
+ * value so the animation does not jump
258
+ * to the end when the user beings to drag.
259
+ */
260
+ .duration(100);
261
+ switch (el.position) {
262
+ case 'middle':
263
+ MAX_SWIPE_DISTANCE = hostElHeight + wrapperElBox.height;
264
+ swipeAnimation.keyframes(SWIPE_UP_DOWN_KEYFRAMES);
265
+ /**
266
+ * Toast can be swiped up or down but
267
+ * should start in the middle of the screen.
268
+ */
269
+ swipeAnimation.progressStart(true, 0.5);
270
+ break;
271
+ case 'top':
272
+ /**
273
+ * The bottom edge of the wrapper
274
+ * includes the distance between the top
275
+ * of the screen and the top of the wrapper
276
+ * as well as the wrapper height so the wrapper
277
+ * can be dragged fully offscreen.
278
+ */
279
+ MAX_SWIPE_DISTANCE = wrapperElBox.bottom;
280
+ swipeAnimation.keyframes([
281
+ { offset: 0, transform: `translateY(${toastPosition.top})` },
282
+ { offset: 1, transform: 'translateY(-100%)' },
283
+ ]);
284
+ swipeAnimation.progressStart(true, 0);
285
+ break;
286
+ case 'bottom':
287
+ default:
288
+ /**
289
+ * This computes the distance between the
290
+ * top of the wrapper and the bottom of the
291
+ * screen including the height of the wrapper
292
+ * element so it can be dragged fully offscreen.
293
+ */
294
+ MAX_SWIPE_DISTANCE = hostElHeight - wrapperElBox.top;
295
+ swipeAnimation.keyframes([
296
+ { offset: 0, transform: `translateY(${toastPosition.bottom})` },
297
+ { offset: 1, transform: 'translateY(100%)' },
298
+ ]);
299
+ swipeAnimation.progressStart(true, 0);
300
+ break;
301
+ }
302
+ const computeStep = (delta) => {
303
+ return (delta * INVERSION_FACTOR) / MAX_SWIPE_DISTANCE;
304
+ };
305
+ const onMove = (detail) => {
306
+ const step = STEP_OFFSET + computeStep(detail.deltaY);
307
+ swipeAnimation.progressStep(step);
308
+ };
309
+ const onEnd = (detail) => {
310
+ const velocity = detail.velocityY;
311
+ const threshold = ((detail.deltaY + velocity * 1000) / MAX_SWIPE_DISTANCE) * INVERSION_FACTOR;
312
+ /**
313
+ * Disable the gesture for the remainder of the animation.
314
+ * It will be re-enabled if the toast animates back to
315
+ * its initial presented position.
316
+ */
317
+ gesture.enable(false);
318
+ let shouldDismiss = true;
319
+ let playTo = 1;
320
+ let step = 0;
321
+ let remainingDistance = 0;
322
+ if (el.position === 'middle') {
323
+ /**
324
+ * A middle positioned Toast appears
325
+ * in the middle of the screen (at animation offset 0.5).
326
+ * As a result, the threshold will be calculated relative
327
+ * to this starting position. In other words at animation offset 0.5
328
+ * the threshold will be 0. We want the middle Toast to be eligible
329
+ * for dismiss when the user has swiped either half way up or down the
330
+ * screen. As a result, we divide DISMISS_THRESHOLD in half. We also
331
+ * consider when the threshold is a negative in the event the
332
+ * user drags up (since the deltaY will also be negative).
333
+ */
334
+ shouldDismiss = threshold >= DISMISS_THRESHOLD / 2 || threshold <= -DISMISS_THRESHOLD / 2;
335
+ /**
336
+ * Since we are replacing the keyframes
337
+ * below the animation always starts from
338
+ * the beginning of the new keyframes.
339
+ * Similarly, we are always playing to
340
+ * the end of the new keyframes.
341
+ */
342
+ playTo = 1;
343
+ step = 0;
344
+ /**
345
+ * The Toast should animate from wherever its
346
+ * current position is to the desired end state.
347
+ *
348
+ * To begin, we get the current position of the
349
+ * Toast for its starting state.
350
+ */
351
+ const wrapperElBox = wrapperEl.getBoundingClientRect();
352
+ const startOffset = wrapperElBox.top - topPosition;
353
+ const startPosition = `${startOffset}px`;
354
+ /**
355
+ * If the deltaY is negative then the user is swiping
356
+ * up, so the Toast should animate to the top of the screen.
357
+ * If the deltaY is positive then the user is swiping
358
+ * down, so the Toast should animate to the bottom of the screen.
359
+ * We also account for when the deltaY is 0, but realistically
360
+ * that should never happen because it means the user did not drag
361
+ * the toast.
362
+ */
363
+ const offsetFactor = detail.deltaY <= 0 ? -1 : 1;
364
+ const endOffset = (topPosition + wrapperElBox.height) * offsetFactor;
365
+ /**
366
+ * If the Toast should dismiss
367
+ * then we need to figure out which edge of
368
+ * the screen it should animate towards.
369
+ * By default, the Toast will come
370
+ * back to its default state in the
371
+ * middle of the screen.
372
+ */
373
+ const endPosition = shouldDismiss ? `${endOffset}px` : '0px';
374
+ const KEYFRAMES = [
375
+ { offset: 0, transform: `translateY(${startPosition})` },
376
+ { offset: 1, transform: `translateY(${endPosition})` },
377
+ ];
378
+ swipeAnimation.keyframes(KEYFRAMES);
379
+ /**
380
+ * Compute the remaining amount of pixels the
381
+ * toast needs to move to be fully dismissed.
382
+ */
383
+ remainingDistance = endOffset - startOffset;
147
384
  }
148
- else if (position === 'bottom') {
385
+ else {
386
+ shouldDismiss = threshold >= DISMISS_THRESHOLD;
387
+ playTo = shouldDismiss ? 1 : 0;
388
+ step = computeStep(detail.deltaY);
149
389
  /**
150
- * Just box.top is the distance from the top edge of the screen
151
- * to the top edge of the anchor. We want to calculate from the
152
- * bottom edge of the screen instead.
390
+ * Compute the remaining amount of pixels the
391
+ * toast needs to move to be fully dismissed.
153
392
  */
154
- offset -= win.innerHeight - box.top;
393
+ const remainingStepAmount = shouldDismiss ? 1 - step : step;
394
+ remainingDistance = remainingStepAmount * MAX_SWIPE_DISTANCE;
155
395
  }
156
396
  /**
157
- * We don't include safe area here because that should already be
158
- * accounted for when checking the position of the anchor.
397
+ * The animation speed should depend on how quickly
398
+ * the user flicks the toast across the screen. However,
399
+ * it should be no slower than 200ms.
400
+ * We use Math.abs on the remainingDistance because that value
401
+ * can be negative when swiping up on a middle position toast.
159
402
  */
160
- return {
161
- top: `${offset}px`,
162
- bottom: `${offset}px`,
163
- };
164
- }
165
- else {
166
- return {
167
- top: `calc(${offset}px + var(--ion-safe-area-top, 0px))`,
168
- bottom: `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`,
169
- };
170
- }
171
- }
172
- /**
173
- * If the anchor element is hidden, getBoundingClientRect()
174
- * will return all 0s for it, which can cause unexpected
175
- * results in the position calculation when animating.
176
- */
177
- function warnIfAnchorIsHidden(positionAnchor, toast) {
178
- if (positionAnchor.offsetParent === null) {
179
- printIonWarning('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);
180
- }
181
- }
403
+ const duration = Math.min(Math.abs(remainingDistance) / Math.abs(velocity), 200);
404
+ swipeAnimation
405
+ .onFinish(() => {
406
+ if (shouldDismiss) {
407
+ onDismiss();
408
+ swipeAnimation.destroy();
409
+ }
410
+ else {
411
+ if (el.position === 'middle') {
412
+ /**
413
+ * If the toast snapped back to
414
+ * the middle of the screen we need
415
+ * to reset the keyframes
416
+ * so the toast can be swiped
417
+ * up or down again.
418
+ */
419
+ swipeAnimation.keyframes(SWIPE_UP_DOWN_KEYFRAMES).progressStart(true, 0.5);
420
+ }
421
+ else {
422
+ swipeAnimation.progressStart(true, 0);
423
+ }
424
+ /**
425
+ * If the toast did not dismiss then
426
+ * the user should be able to swipe again.
427
+ */
428
+ gesture.enable(true);
429
+ }
430
+ /**
431
+ * This must be a one time callback
432
+ * otherwise a new callback will
433
+ * be added every time onEnd runs.
434
+ */
435
+ }, { oneTimeCallback: true })
436
+ .progressEnd(playTo, step, duration);
437
+ };
438
+ const gesture = createGesture({
439
+ el: wrapperEl,
440
+ gestureName: 'toast-swipe-to-dismiss',
441
+ gesturePriority: OVERLAY_GESTURE_PRIORITY,
442
+ /**
443
+ * Toast only supports vertical swipes.
444
+ * This needs to be updated if we later
445
+ * support horizontal swipes.
446
+ */
447
+ direction: 'y',
448
+ onMove,
449
+ onEnd,
450
+ });
451
+ return gesture;
452
+ };
182
453
 
183
454
  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}@supports (inset-inline-start: 0){:host{inset-inline-start:0}}@supports not (inset-inline-start: 0){:host{left:0}:host-context([dir=rtl]){left:unset;right:unset;right:0}@supports selector(:dir(rtl)){:host(:dir(rtl)){left:unset;right:unset;right: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)}@supports (inset-inline-start: 0){.toast-wrapper{inset-inline-start:var(--start);inset-inline-end:var(--end)}}@supports not (inset-inline-start: 0){.toast-wrapper{left:var(--start);right:var(--end)}:host-context([dir=rtl]) .toast-wrapper{left:unset;right:unset;left:var(--end);right:var(--start)}[dir=rtl] .toast-wrapper{left:unset;right:unset;left:var(--end);right:var(--start)}@supports selector(:dir(rtl)){.toast-wrapper:dir(rtl){left:unset;right:unset;left:var(--end);right:var(--start)}}}.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;pointer-events:auto;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, #f2f2f2);--border-radius:14px;--button-color:var(--ion-color-primary, #3880ff);--color:var(--ion-color-step-850, #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}}";
184
455
 
@@ -209,6 +480,41 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
209
480
  this.callButtonHandler(cancelButton);
210
481
  }
211
482
  };
483
+ /**
484
+ * Create a new swipe gesture so Toast
485
+ * can be swiped to dismiss.
486
+ */
487
+ this.createSwipeGesture = (toastPosition) => {
488
+ const gesture = (this.gesture = createSwipeToDismissGesture(this.el, toastPosition, () => {
489
+ /**
490
+ * If the gesture completed then
491
+ * we should dismiss the toast.
492
+ */
493
+ this.dismiss(undefined, GESTURE);
494
+ }));
495
+ gesture.enable(true);
496
+ };
497
+ /**
498
+ * Destroy an existing swipe gesture
499
+ * so Toast can no longer be swiped to dismiss.
500
+ */
501
+ this.destroySwipeGesture = () => {
502
+ const { gesture } = this;
503
+ if (gesture === undefined) {
504
+ return;
505
+ }
506
+ gesture.destroy();
507
+ this.gesture = undefined;
508
+ };
509
+ /**
510
+ * Returns `true` if swipeGesture
511
+ * is configured to a value that enables the swipe behavior.
512
+ * Returns `false` otherwise.
513
+ */
514
+ this.prefersSwipeGesture = () => {
515
+ const { swipeGesture } = this;
516
+ return swipeGesture === 'vertical';
517
+ };
212
518
  this.revealContentToScreenReader = false;
213
519
  this.overlayIndex = undefined;
214
520
  this.delegate = undefined;
@@ -229,9 +535,37 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
229
535
  this.animated = true;
230
536
  this.icon = undefined;
231
537
  this.htmlAttributes = undefined;
538
+ this.swipeGesture = undefined;
232
539
  this.isOpen = false;
233
540
  this.trigger = undefined;
234
541
  }
542
+ swipeGestureChanged() {
543
+ /**
544
+ * If the Toast is presented, then we need to destroy
545
+ * any actives gestures before a new gesture is potentially
546
+ * created below.
547
+ *
548
+ * If the Toast is dismissed, then no gesture should be available
549
+ * since the Toast is not visible. This case should never
550
+ * happen since the "dismiss" method handles destroying
551
+ * any active swipe gestures, but we keep this code
552
+ * around to handle the first case.
553
+ */
554
+ this.destroySwipeGesture();
555
+ /**
556
+ * A new swipe gesture should only be created
557
+ * if the Toast is presented. If the Toast is not
558
+ * yet presented then the "present" method will
559
+ * handle calling the swipe gesture setup function.
560
+ */
561
+ if (this.presented && this.prefersSwipeGesture()) {
562
+ /**
563
+ * If the Toast is presented then
564
+ * lastPresentedPosition is defined.
565
+ */
566
+ this.createSwipeGesture(this.lastPresentedPosition);
567
+ }
568
+ }
235
569
  onIsOpenChange(newValue, oldValue) {
236
570
  if (newValue === true && oldValue === false) {
237
571
  this.present();
@@ -293,6 +627,14 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
293
627
  if (this.duration > 0) {
294
628
  this.durationTimeout = setTimeout(() => this.dismiss(undefined, 'timeout'), this.duration);
295
629
  }
630
+ /**
631
+ * If the Toast has a swipe gesture then we can
632
+ * create the gesture so users can swipe the
633
+ * presented Toast.
634
+ */
635
+ if (this.prefersSwipeGesture()) {
636
+ this.createSwipeGesture(animationPosition);
637
+ }
296
638
  unlock();
297
639
  }
298
640
  /**
@@ -327,6 +669,11 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
327
669
  this.revealContentToScreenReader = false;
328
670
  }
329
671
  this.lastPresentedPosition = undefined;
672
+ /**
673
+ * If the Toast has a swipe gesture then we can
674
+ * safely destroy it now that it is dismissed.
675
+ */
676
+ this.destroySwipeGesture();
330
677
  unlock();
331
678
  return dismissed;
332
679
  }
@@ -471,6 +818,7 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
471
818
  }
472
819
  get el() { return this; }
473
820
  static get watchers() { return {
821
+ "swipeGesture": ["swipeGestureChanged"],
474
822
  "isOpen": ["onIsOpenChange"],
475
823
  "trigger": ["triggerChanged"]
476
824
  }; }
@@ -498,6 +846,7 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
498
846
  "animated": [4],
499
847
  "icon": [1],
500
848
  "htmlAttributes": [16],
849
+ "swipeGesture": [1, "swipe-gesture"],
501
850
  "isOpen": [4, "is-open"],
502
851
  "trigger": [1],
503
852
  "revealContentToScreenReader": [32],
@@ -506,6 +855,7 @@ const Toast = /*@__PURE__*/ proxyCustomElement(class Toast extends HTMLElement {
506
855
  "onDidDismiss": [64],
507
856
  "onWillDismiss": [64]
508
857
  }, undefined, {
858
+ "swipeGesture": ["swipeGestureChanged"],
509
859
  "isOpen": ["onIsOpenChange"],
510
860
  "trigger": ["triggerChanged"]
511
861
  }]);
@@ -562,6 +562,7 @@ const safeCall = (handler, arg) => {
562
562
  };
563
563
  const BACKDROP = 'backdrop';
564
564
  const GESTURE = 'gesture';
565
+ const OVERLAY_GESTURE_PRIORITY = 39;
565
566
  /**
566
567
  * Creates a delegate controller.
567
568
  *
@@ -689,4 +690,4 @@ const createTriggerController = () => {
689
690
  };
690
691
  };
691
692
 
692
- export { BACKDROP as B, GESTURE as G, alertController as a, actionSheetController as b, popoverController as c, createDelegateController as d, createTriggerController as e, present as f, dismiss as g, eventMethod as h, isCancel as i, prepareOverlay as j, setOverlayId as k, loadingController as l, modalController as m, focusFirstDescendant as n, getPresentedOverlay as o, pickerController as p, activeAnimations as q, safeCall as s, toastController as t };
693
+ export { BACKDROP as B, GESTURE as G, OVERLAY_GESTURE_PRIORITY as O, alertController as a, actionSheetController as b, popoverController as c, createDelegateController as d, createTriggerController as e, present as f, dismiss as g, eventMethod as h, isCancel as i, prepareOverlay as j, setOverlayId as k, loadingController as l, modalController as m, focusFirstDescendant as n, getPresentedOverlay as o, pickerController as p, activeAnimations as q, safeCall as s, toastController as t };