@ionic/core 7.4.2-dev.11695927273.1323de22 → 7.4.2-dev.11696878562.16da82aa

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 (48) hide show
  1. package/components/alert.js +8 -0
  2. package/components/index.js +1 -0
  3. package/components/ion-select.js +1 -1
  4. package/components/ion-toast.js +144 -113
  5. package/dist/cjs/index.cjs.js +2 -0
  6. package/dist/cjs/ion-alert.cjs.entry.js +8 -0
  7. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  8. package/dist/cjs/ion-toast.cjs.entry.js +163 -132
  9. package/dist/collection/components/alert/alert.js +8 -0
  10. package/dist/collection/components/alert/test/a11y/alert.e2e.js +11 -0
  11. package/dist/collection/components/select/select.js +1 -1
  12. package/dist/collection/components/toast/animations/ios.enter.js +1 -39
  13. package/dist/collection/components/toast/animations/ios.leave.js +4 -9
  14. package/dist/collection/components/toast/animations/md.enter.js +1 -40
  15. package/dist/collection/components/toast/animations/utils.js +85 -0
  16. package/dist/collection/components/toast/toast.js +58 -31
  17. package/dist/collection/index.js +1 -0
  18. package/dist/docs.json +3 -7
  19. package/dist/esm/index.js +1 -0
  20. package/dist/esm/ion-alert.entry.js +8 -0
  21. package/dist/esm/ion-select_3.entry.js +1 -1
  22. package/dist/esm/ion-toast.entry.js +144 -113
  23. package/dist/esm-es5/index.js +1 -1
  24. package/dist/esm-es5/ion-alert.entry.js +1 -1
  25. package/dist/esm-es5/ion-select_3.entry.js +1 -1
  26. package/dist/esm-es5/ion-toast.entry.js +1 -1
  27. package/dist/ionic/index.esm.js +1 -1
  28. package/dist/ionic/ionic.esm.js +1 -1
  29. package/dist/ionic/p-06c94b76.entry.js +4 -0
  30. package/dist/ionic/{p-2df840d9.system.entry.js → p-1e207aa7.system.entry.js} +1 -1
  31. package/dist/ionic/p-2bea5600.entry.js +4 -0
  32. package/dist/ionic/p-4e312f80.system.entry.js +4 -0
  33. package/dist/ionic/p-5f6605c4.entry.js +4 -0
  34. package/dist/ionic/{p-5d522814.system.entry.js → p-6cb04881.system.entry.js} +1 -1
  35. package/dist/ionic/p-6edbc09b.system.js +1 -1
  36. package/dist/ionic/p-8a1faf2c.system.js +4 -0
  37. package/dist/types/components/toast/animations/ios.leave.d.ts +2 -2
  38. package/dist/types/components/toast/animations/utils.d.ts +18 -0
  39. package/dist/types/components/toast/toast-interface.d.ts +8 -3
  40. package/dist/types/components/toast/toast.d.ts +12 -0
  41. package/dist/types/index.d.ts +1 -0
  42. package/hydrate/index.js +153 -114
  43. package/package.json +1 -1
  44. package/dist/ionic/p-1f448082.system.entry.js +0 -4
  45. package/dist/ionic/p-35d1c3bb.system.js +0 -4
  46. package/dist/ionic/p-8a26eaba.entry.js +0 -4
  47. package/dist/ionic/p-990aaa2f.entry.js +0 -4
  48. package/dist/ionic/p-c44645f2.entry.js +0 -4
@@ -5,11 +5,11 @@
5
5
 
6
6
  Object.defineProperty(exports, '__esModule', { value: true });
7
7
 
8
- const index$1 = require('./index-6f887430.js');
8
+ const index$2 = require('./index-6f887430.js');
9
9
  const config = require('./config-d5882735.js');
10
10
  const helpers = require('./helpers-ea4ccbcb.js');
11
11
  const lockController = require('./lock-controller-4ae2eb59.js');
12
- const index$2 = require('./index-cc7dfb7c.js');
12
+ const index$1 = require('./index-cc7dfb7c.js');
13
13
  const overlays = require('./overlays-817dca17.js');
14
14
  const theme = require('./theme-fbc56b3b.js');
15
15
  const ionicGlobal = require('./ionic-global-14a0604b.js');
@@ -24,46 +24,9 @@ require('./hardware-back-button-b67c8e75.js');
24
24
  const iosEnterAnimation = (baseEl, opts) => {
25
25
  const baseAnimation = animation.createAnimation();
26
26
  const wrapperAnimation = animation.createAnimation();
27
- const { position, positionAnchor } = opts;
27
+ const { position, top, bottom } = opts;
28
28
  const root = helpers.getElementRoot(baseEl);
29
29
  const wrapperEl = root.querySelector('.toast-wrapper');
30
- /**
31
- * Start with a predefined offset from the edge the toast will be
32
- * positioned relative to. By default, this will be the top edge
33
- * of the screen.
34
- */
35
- let offset = position === 'top' ? 10 : -10;
36
- let top, bottom;
37
- /**
38
- * If positionAnchor is defined, add in the distance from the top
39
- * of the screen to the target anchor edge. For position="top", the
40
- * bottom anchor edge is targeted. For position="bottom", the top
41
- * anchor edge is targeted.
42
- */
43
- if (positionAnchor && index.win) {
44
- const box = positionAnchor.getBoundingClientRect();
45
- if (position === 'top') {
46
- offset += box.bottom;
47
- }
48
- else if (position === 'bottom') {
49
- /**
50
- * Just box.top is the distance from the top edge of the screen
51
- * to the top edge of the anchor. We want to calculate from the
52
- * bottom edge of the screen instead.
53
- */
54
- offset -= index.win.innerHeight - box.top;
55
- }
56
- /**
57
- * We don't include safe area here because that should already be
58
- * accounted for when checking the position of the anchor.
59
- */
60
- top = `${offset}px`;
61
- bottom = `${offset}px`;
62
- }
63
- else {
64
- top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;
65
- bottom = `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`;
66
- }
67
30
  wrapperAnimation.addElement(wrapperEl);
68
31
  switch (position) {
69
32
  case 'top':
@@ -84,27 +47,22 @@ const iosEnterAnimation = (baseEl, opts) => {
84
47
  /**
85
48
  * iOS Toast Leave Animation
86
49
  */
87
- const iosLeaveAnimation = (baseEl, position) => {
50
+ const iosLeaveAnimation = (baseEl, opts) => {
88
51
  const baseAnimation = animation.createAnimation();
89
52
  const wrapperAnimation = animation.createAnimation();
53
+ const { position, top, bottom } = opts;
90
54
  const root = helpers.getElementRoot(baseEl);
91
55
  const wrapperEl = root.querySelector('.toast-wrapper');
92
56
  wrapperAnimation.addElement(wrapperEl);
93
- /**
94
- * For top/bottom positions, we use to() instead of fromTo() so that if
95
- * the positionAnchor prop was used, we don't need to recalculate the
96
- * toast and/or anchor's position and duplicate work from the enter
97
- * animation.
98
- */
99
57
  switch (position) {
100
58
  case 'top':
101
- wrapperAnimation.to('transform', 'translateY(-100%)');
59
+ wrapperAnimation.fromTo('transform', `translateY(${top})`, 'translateY(-100%)');
102
60
  break;
103
61
  case 'middle':
104
62
  wrapperAnimation.fromTo('opacity', 0.99, 0);
105
63
  break;
106
64
  default:
107
- wrapperAnimation.to('transform', 'translateY(100%)');
65
+ wrapperAnimation.fromTo('transform', `translateY(${bottom})`, 'translateY(100%)');
108
66
  break;
109
67
  }
110
68
  return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(300).addAnimation(wrapperAnimation);
@@ -116,47 +74,9 @@ const iosLeaveAnimation = (baseEl, position) => {
116
74
  const mdEnterAnimation = (baseEl, opts) => {
117
75
  const baseAnimation = animation.createAnimation();
118
76
  const wrapperAnimation = animation.createAnimation();
119
- const { position, positionAnchor } = opts;
77
+ const { position, top, bottom } = opts;
120
78
  const root = helpers.getElementRoot(baseEl);
121
79
  const wrapperEl = root.querySelector('.toast-wrapper');
122
- /**
123
- * Start with a predefined offset from the edge the toast will be
124
- * positioned relative to. By default, this will be the top edge
125
- * of the screen for position="top", or the bottom edge for
126
- * position="bottom".
127
- */
128
- let offset = 8;
129
- let top, bottom;
130
- /**
131
- * If positionAnchor is defined, add in the distance from the target
132
- * screen edge to the target anchor edge. For position="top", the
133
- * bottom anchor edge is targeted. For position="bottom", the top
134
- * anchor edge is targeted.
135
- */
136
- if (positionAnchor && index.win) {
137
- const box = positionAnchor.getBoundingClientRect();
138
- if (position === 'top') {
139
- offset += box.bottom;
140
- }
141
- else if (position === 'bottom') {
142
- /**
143
- * Just box.top is the distance from the top edge of the screen
144
- * to the top edge of the anchor. We want to calculate from the
145
- * bottom edge of the screen instead.
146
- */
147
- offset += index.win.innerHeight - box.top;
148
- }
149
- /**
150
- * We don't include safe area here because that should already be
151
- * accounted for when checking the position of the anchor.
152
- */
153
- top = `${offset}px`;
154
- bottom = `${offset}px`;
155
- }
156
- else {
157
- top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;
158
- bottom = `calc(${offset}px + var(--ion-safe-area-bottom, 0px))`;
159
- }
160
80
  wrapperAnimation.addElement(wrapperEl);
161
81
  switch (position) {
162
82
  case 'top':
@@ -188,21 +108,102 @@ const mdLeaveAnimation = (baseEl) => {
188
108
  return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(300).addAnimation(wrapperAnimation);
189
109
  };
190
110
 
111
+ /**
112
+ * Calculate the CSS top and bottom position of the toast, to be used
113
+ * as starting points for the animation keyframes.
114
+ *
115
+ * Note that MD animates bottom-positioned toasts using style.bottom,
116
+ * which calculates from the bottom edge of the screen, while iOS uses
117
+ * translateY, which calculates from the top edge of the screen. This
118
+ * is why the bottom calculates differ slightly between modes.
119
+ *
120
+ * @param position The value of the toast's position prop.
121
+ * @param positionAnchor The element the toast should be anchored to,
122
+ * if applicable.
123
+ * @param mode The toast component's mode (md, ios, etc).
124
+ * @param toast A reference to the toast element itself.
125
+ */
126
+ function getAnimationPosition(position, positionAnchor, mode, toast) {
127
+ /**
128
+ * Start with a predefined offset from the edge the toast will be
129
+ * positioned relative to, whether on the screen or anchor element.
130
+ */
131
+ let offset;
132
+ if (mode === 'md') {
133
+ offset = 8;
134
+ }
135
+ else {
136
+ offset = position === 'top' ? 10 : -10;
137
+ }
138
+ /**
139
+ * If positionAnchor is defined, add in the distance from the target
140
+ * screen edge to the target anchor edge. For position="top", the
141
+ * bottom anchor edge is targeted. For position="bottom", the top
142
+ * anchor edge is targeted.
143
+ */
144
+ if (positionAnchor && index.win) {
145
+ warnIfAnchorIsHidden(positionAnchor, toast);
146
+ const box = positionAnchor.getBoundingClientRect();
147
+ if (position === 'top') {
148
+ offset += box.bottom;
149
+ }
150
+ else if (position === 'bottom') {
151
+ /**
152
+ * Just box.top is the distance from the top edge of the screen
153
+ * to the top edge of the anchor. We want to calculate from the
154
+ * bottom edge of the screen instead.
155
+ */
156
+ if (mode === 'md') {
157
+ offset += index.win.innerHeight - box.top;
158
+ }
159
+ else {
160
+ offset -= index.win.innerHeight - box.top;
161
+ }
162
+ }
163
+ /**
164
+ * We don't include safe area here because that should already be
165
+ * accounted for when checking the position of the anchor.
166
+ */
167
+ return {
168
+ top: `${offset}px`,
169
+ bottom: `${offset}px`,
170
+ };
171
+ }
172
+ else {
173
+ return {
174
+ top: `calc(${offset}px + var(--ion-safe-area-top, 0px))`,
175
+ bottom: mode === 'md'
176
+ ? `calc(${offset}px + var(--ion-safe-area-bottom, 0px))`
177
+ : `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`,
178
+ };
179
+ }
180
+ }
181
+ /**
182
+ * If the anchor element is hidden, getBoundingClientRect()
183
+ * will return all 0s for it, which can cause unexpected
184
+ * results in the position calculation when animating.
185
+ */
186
+ function warnIfAnchorIsHidden(positionAnchor, toast) {
187
+ if (positionAnchor.offsetParent === null) {
188
+ index$1.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);
189
+ }
190
+ }
191
+
191
192
  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-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-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;--start:10px;--end:10px;font-size:14px}.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-top{-webkit-transform:translate3d(0, -100%, 0);transform:translate3d(0, -100%, 0);top:0}.toast-wrapper.toast-middle{opacity:0.01}.toast-wrapper.toast-bottom{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0);bottom:0}.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;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:17px;font-weight:500;overflow:hidden}.toast-button.ion-activated{opacity:0.4}@media (any-hover: hover){.toast-button:hover{opacity:0.6}}";
192
193
 
193
194
  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}@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-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-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, #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, #3880ff);--color:var(--ion-color-step-50, #f2f2f2);--max-width:700px;--start:8px;--end:8px;font-size:14px}.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:20px}.toast-message{line-height:20px}.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:14px;font-weight:500;letter-spacing:0.84px;text-transform:uppercase;overflow:hidden}.toast-button-cancel{color:var(--ion-color-step-100, #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, 56, 128, 255), 0.08)}.toast-button-cancel:hover{background-color:rgba(var(--ion-background-color-rgb, 255, 255, 255), 0.08)}}";
194
195
 
195
196
  const Toast = class {
196
197
  constructor(hostRef) {
197
- index$1.registerInstance(this, hostRef);
198
- this.didPresent = index$1.createEvent(this, "ionToastDidPresent", 7);
199
- this.willPresent = index$1.createEvent(this, "ionToastWillPresent", 7);
200
- this.willDismiss = index$1.createEvent(this, "ionToastWillDismiss", 7);
201
- this.didDismiss = index$1.createEvent(this, "ionToastDidDismiss", 7);
202
- this.didPresentShorthand = index$1.createEvent(this, "didPresent", 7);
203
- this.willPresentShorthand = index$1.createEvent(this, "willPresent", 7);
204
- this.willDismissShorthand = index$1.createEvent(this, "willDismiss", 7);
205
- this.didDismissShorthand = index$1.createEvent(this, "didDismiss", 7);
198
+ index$2.registerInstance(this, hostRef);
199
+ this.didPresent = index$2.createEvent(this, "ionToastDidPresent", 7);
200
+ this.willPresent = index$2.createEvent(this, "ionToastWillPresent", 7);
201
+ this.willDismiss = index$2.createEvent(this, "ionToastWillDismiss", 7);
202
+ this.didDismiss = index$2.createEvent(this, "ionToastDidDismiss", 7);
203
+ this.didPresentShorthand = index$2.createEvent(this, "didPresent", 7);
204
+ this.willPresentShorthand = index$2.createEvent(this, "willPresent", 7);
205
+ this.willDismissShorthand = index$2.createEvent(this, "willDismiss", 7);
206
+ this.didDismissShorthand = index$2.createEvent(this, "didDismiss", 7);
206
207
  this.delegateController = overlays.createDelegateController(this);
207
208
  this.lockController = lockController.createLockController();
208
209
  this.triggerController = overlays.createTriggerController();
@@ -277,32 +278,18 @@ const Toast = class {
277
278
  async present() {
278
279
  const unlock = await this.lockController.lock();
279
280
  await this.delegateController.attachViewToDom();
280
- const { position, positionAnchor } = this;
281
- let anchor;
282
- if (position === 'middle' && positionAnchor !== undefined) {
283
- index$2.printIonWarning('The positionAnchor property is ignored when using position="middle".', this.el);
284
- }
285
- else if (typeof positionAnchor === 'string') {
286
- /**
287
- * If the anchor is defined as an ID, find the element.
288
- * We do this on every present so the toast doesn't need
289
- * to account for the surrounding DOM changing since the
290
- * last time it was presented.
291
- */
292
- const foundEl = document.getElementById(positionAnchor);
293
- if (foundEl === null) {
294
- index$2.printIonWarning(`An anchor element with an ID of ${positionAnchor} was not found in the DOM.`, this.el);
295
- }
296
- else {
297
- anchor = foundEl;
298
- }
299
- }
300
- else {
301
- anchor = positionAnchor;
302
- }
281
+ const { el, position } = this;
282
+ const anchor = this.getAnchorElement();
283
+ const animationPosition = getAnimationPosition(position, anchor, ionicGlobal.getIonMode(this), el);
284
+ /**
285
+ * Cache the calculated position of the toast, so we can re-use it
286
+ * in the dismiss animation.
287
+ */
288
+ this.lastPresentedPosition = animationPosition;
303
289
  await overlays.present(this, 'toastEnter', iosEnterAnimation, mdEnterAnimation, {
304
290
  position,
305
- positionAnchor: anchor,
291
+ top: animationPosition.top,
292
+ bottom: animationPosition.bottom,
306
293
  });
307
294
  /**
308
295
  * Content is revealed to screen readers after
@@ -325,15 +312,28 @@ const Toast = class {
325
312
  * Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
326
313
  */
327
314
  async dismiss(data, role) {
315
+ var _a, _b;
328
316
  const unlock = await this.lockController.lock();
329
- if (this.durationTimeout) {
330
- clearTimeout(this.durationTimeout);
317
+ const { durationTimeout, position, lastPresentedPosition } = this;
318
+ if (durationTimeout) {
319
+ clearTimeout(durationTimeout);
331
320
  }
332
- const dismissed = await overlays.dismiss(this, data, role, 'toastLeave', iosLeaveAnimation, mdLeaveAnimation, this.position);
321
+ const dismissed = await overlays.dismiss(this, data, role, 'toastLeave', iosLeaveAnimation, mdLeaveAnimation,
322
+ /**
323
+ * Fetch the cached position that was calculated back in the present
324
+ * animation. We always want to animate the dismiss from the same
325
+ * position the present stopped at, so the animation looks continuous.
326
+ */
327
+ {
328
+ position,
329
+ top: (_a = lastPresentedPosition === null || lastPresentedPosition === void 0 ? void 0 : lastPresentedPosition.top) !== null && _a !== void 0 ? _a : '',
330
+ bottom: (_b = lastPresentedPosition === null || lastPresentedPosition === void 0 ? void 0 : lastPresentedPosition.bottom) !== null && _b !== void 0 ? _b : '',
331
+ });
333
332
  if (dismissed) {
334
333
  this.delegateController.removeViewFromDom();
335
334
  this.revealContentToScreenReader = false;
336
335
  }
336
+ this.lastPresentedPosition = undefined;
337
337
  unlock();
338
338
  return dismissed;
339
339
  }
@@ -357,6 +357,37 @@ const Toast = class {
357
357
  : [];
358
358
  return buttons;
359
359
  }
360
+ /**
361
+ * Returns the element specified by the positionAnchor prop,
362
+ * or undefined if prop's value is an ID string and the element
363
+ * is not found in the DOM.
364
+ */
365
+ getAnchorElement() {
366
+ const { position, positionAnchor, el } = this;
367
+ if (position === 'middle' && positionAnchor !== undefined) {
368
+ index$1.printIonWarning('The positionAnchor property is ignored when using position="middle".', this.el);
369
+ return undefined;
370
+ }
371
+ if (typeof positionAnchor === 'string') {
372
+ /**
373
+ * If the anchor is defined as an ID, find the element.
374
+ * We do this on every present so the toast doesn't need
375
+ * to account for the surrounding DOM changing since the
376
+ * last time it was presented.
377
+ */
378
+ const foundEl = document.getElementById(positionAnchor);
379
+ if (foundEl === null) {
380
+ index$1.printIonWarning(`An anchor element with an ID of "${positionAnchor}" was not found in the DOM.`, el);
381
+ return undefined;
382
+ }
383
+ return foundEl;
384
+ }
385
+ if (positionAnchor instanceof HTMLElement) {
386
+ return positionAnchor;
387
+ }
388
+ index$1.printIonWarning('Invalid positionAnchor value:', positionAnchor, el);
389
+ return undefined;
390
+ }
360
391
  async buttonClick(button) {
361
392
  const role = button.role;
362
393
  if (overlays.isCancel(role)) {
@@ -394,7 +425,7 @@ const Toast = class {
394
425
  'toast-button-group': true,
395
426
  [`toast-button-group-${side}`]: true,
396
427
  };
397
- return (index$1.h("div", { class: buttonGroupsClasses }, buttons.map((b) => (index$1.h("button", Object.assign({}, b.htmlAttributes, { type: "button", class: buttonClass(b), tabIndex: 0, onClick: () => this.buttonClick(b), part: buttonPart(b) }), index$1.h("div", { class: "toast-button-inner" }, b.icon && (index$1.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$1.h("ion-ripple-effect", { type: b.icon !== undefined && b.text === undefined ? 'unbounded' : 'bounded' })))))));
428
+ return (index$2.h("div", { class: buttonGroupsClasses }, buttons.map((b) => (index$2.h("button", Object.assign({}, b.htmlAttributes, { type: "button", class: buttonClass(b), tabIndex: 0, onClick: () => this.buttonClick(b), part: buttonPart(b) }), index$2.h("div", { class: "toast-button-inner" }, b.icon && (index$2.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$2.h("ion-ripple-effect", { type: b.icon !== undefined && b.text === undefined ? 'unbounded' : 'bounded' })))))));
398
429
  }
399
430
  /**
400
431
  * Render the `message` property.
@@ -404,9 +435,9 @@ const Toast = class {
404
435
  renderToastMessage(key, ariaHidden = null) {
405
436
  const { customHTMLEnabled, message } = this;
406
437
  if (customHTMLEnabled) {
407
- return (index$1.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message", innerHTML: config.sanitizeDOMString(message) }));
438
+ return (index$2.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message", innerHTML: config.sanitizeDOMString(message) }));
408
439
  }
409
- return (index$1.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message" }, message));
440
+ return (index$2.h("div", { key: key, "aria-hidden": ariaHidden, class: "toast-message", part: "message" }, message));
410
441
  }
411
442
  /**
412
443
  * Render the `header` property.
@@ -414,7 +445,7 @@ const Toast = class {
414
445
  * @param ariaHidden - If "true" then content will be hidden from screen readers.
415
446
  */
416
447
  renderHeader(key, ariaHidden = null) {
417
- return (index$1.h("div", { key: key, class: "toast-header", "aria-hidden": ariaHidden, part: "header" }, this.header));
448
+ return (index$2.h("div", { key: key, class: "toast-header", "aria-hidden": ariaHidden, part: "header" }, this.header));
418
449
  }
419
450
  render() {
420
451
  const { layout, el, revealContentToScreenReader, header, message } = this;
@@ -432,13 +463,13 @@ const Toast = class {
432
463
  * used with one type of button.
433
464
  */
434
465
  if (layout === 'stacked' && startButtons.length > 0 && endButtons.length > 0) {
435
- index$2.printIonWarning('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);
466
+ index$1.printIonWarning('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);
436
467
  }
437
- return (index$1.h(index$1.Host, Object.assign({ tabindex: "-1" }, this.htmlAttributes, { style: {
468
+ return (index$2.h(index$2.Host, Object.assign({ tabindex: "-1" }, this.htmlAttributes, { style: {
438
469
  zIndex: `${60000 + this.overlayIndex}`,
439
- }, 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$1.h("div", { class: wrapperClass }, index$1.h("div", { class: "toast-container", part: "container" }, this.renderButtons(startButtons, 'start'), this.icon !== undefined && (index$1.h("ion-icon", { class: "toast-icon", part: "icon", icon: this.icon, lazy: false, "aria-hidden": "true" })), index$1.h("div", { 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')))));
470
+ }, 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$2.h("div", { class: wrapperClass }, index$2.h("div", { class: "toast-container", part: "container" }, this.renderButtons(startButtons, 'start'), this.icon !== undefined && (index$2.h("ion-icon", { class: "toast-icon", part: "icon", icon: this.icon, lazy: false, "aria-hidden": "true" })), index$2.h("div", { 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')))));
440
471
  }
441
- get el() { return index$1.getElement(this); }
472
+ get el() { return index$2.getElement(this); }
442
473
  static get watchers() { return {
443
474
  "isOpen": ["onIsOpenChange"],
444
475
  "trigger": ["triggerChanged"]
@@ -73,6 +73,14 @@ export class Alert {
73
73
  }
74
74
  onKeydown(ev) {
75
75
  const inputTypes = new Set(this.processedInputs.map((i) => i.type));
76
+ /**
77
+ * Based on keyboard navigation requirements, the
78
+ * checkbox should not respond to the enter keydown event.
79
+ */
80
+ if (inputTypes.has('checkbox') && ev.key === 'Enter') {
81
+ ev.preventDefault();
82
+ return;
83
+ }
76
84
  // The only inputs we want to navigate between using arrow keys are the radios
77
85
  // ignore the keydown event if it is not on a radio button
78
86
  if (!inputTypes.has('radio') ||
@@ -54,5 +54,16 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
54
54
  await expect(alertButton).toHaveAttribute('aria-labelledby', 'close-label');
55
55
  await expect(alertButton).toHaveAttribute('aria-label', 'close button');
56
56
  });
57
+ test('should not toggle the checkbox when pressing the Enter key', async ({ page }) => {
58
+ const didPresent = await page.spyOnEvent('ionAlertDidPresent');
59
+ const button = page.locator('#checkbox');
60
+ await button.click();
61
+ await didPresent.next();
62
+ const alertCheckbox = page.locator('ion-alert .alert-checkbox');
63
+ const ariaChecked = await alertCheckbox.getAttribute('aria-checked');
64
+ await expect(alertCheckbox).toHaveAttribute('aria-checked', ariaChecked);
65
+ await alertCheckbox.press('Enter');
66
+ await expect(alertCheckbox).toHaveAttribute('aria-checked', ariaChecked);
67
+ });
57
68
  });
58
69
  });
@@ -642,7 +642,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
642
642
  }
643
643
  renderListbox() {
644
644
  const { disabled, inputId, isExpanded } = this;
645
- return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "listbox", "aria-expanded": `${isExpanded}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
645
+ return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
646
646
  }
647
647
  render() {
648
648
  const { legacyFormController } = this;
@@ -2,7 +2,6 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { createAnimation } from "../../../utils/animation/animation";
5
- import { win } from "../../../utils/browser/index";
6
5
  import { getElementRoot } from "../../../utils/helpers";
7
6
  /**
8
7
  * iOS Toast Enter Animation
@@ -10,46 +9,9 @@ import { getElementRoot } from "../../../utils/helpers";
10
9
  export const iosEnterAnimation = (baseEl, opts) => {
11
10
  const baseAnimation = createAnimation();
12
11
  const wrapperAnimation = createAnimation();
13
- const { position, positionAnchor } = opts;
12
+ const { position, top, bottom } = opts;
14
13
  const root = getElementRoot(baseEl);
15
14
  const wrapperEl = root.querySelector('.toast-wrapper');
16
- /**
17
- * Start with a predefined offset from the edge the toast will be
18
- * positioned relative to. By default, this will be the top edge
19
- * of the screen.
20
- */
21
- let offset = position === 'top' ? 10 : -10;
22
- let top, bottom;
23
- /**
24
- * If positionAnchor is defined, add in the distance from the top
25
- * of the screen to the target anchor edge. For position="top", the
26
- * bottom anchor edge is targeted. For position="bottom", the top
27
- * anchor edge is targeted.
28
- */
29
- if (positionAnchor && win) {
30
- const box = positionAnchor.getBoundingClientRect();
31
- if (position === 'top') {
32
- offset += box.bottom;
33
- }
34
- else if (position === 'bottom') {
35
- /**
36
- * Just box.top is the distance from the top edge of the screen
37
- * to the top edge of the anchor. We want to calculate from the
38
- * bottom edge of the screen instead.
39
- */
40
- offset -= win.innerHeight - box.top;
41
- }
42
- /**
43
- * We don't include safe area here because that should already be
44
- * accounted for when checking the position of the anchor.
45
- */
46
- top = `${offset}px`;
47
- bottom = `${offset}px`;
48
- }
49
- else {
50
- top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;
51
- bottom = `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`;
52
- }
53
15
  wrapperAnimation.addElement(wrapperEl);
54
16
  switch (position) {
55
17
  case 'top':
@@ -6,27 +6,22 @@ import { getElementRoot } from "../../../utils/helpers";
6
6
  /**
7
7
  * iOS Toast Leave Animation
8
8
  */
9
- export const iosLeaveAnimation = (baseEl, position) => {
9
+ export const iosLeaveAnimation = (baseEl, opts) => {
10
10
  const baseAnimation = createAnimation();
11
11
  const wrapperAnimation = createAnimation();
12
+ const { position, top, bottom } = opts;
12
13
  const root = getElementRoot(baseEl);
13
14
  const wrapperEl = root.querySelector('.toast-wrapper');
14
15
  wrapperAnimation.addElement(wrapperEl);
15
- /**
16
- * For top/bottom positions, we use to() instead of fromTo() so that if
17
- * the positionAnchor prop was used, we don't need to recalculate the
18
- * toast and/or anchor's position and duplicate work from the enter
19
- * animation.
20
- */
21
16
  switch (position) {
22
17
  case 'top':
23
- wrapperAnimation.to('transform', 'translateY(-100%)');
18
+ wrapperAnimation.fromTo('transform', `translateY(${top})`, 'translateY(-100%)');
24
19
  break;
25
20
  case 'middle':
26
21
  wrapperAnimation.fromTo('opacity', 0.99, 0);
27
22
  break;
28
23
  default:
29
- wrapperAnimation.to('transform', 'translateY(100%)');
24
+ wrapperAnimation.fromTo('transform', `translateY(${bottom})`, 'translateY(100%)');
30
25
  break;
31
26
  }
32
27
  return baseAnimation.easing('cubic-bezier(.36,.66,.04,1)').duration(300).addAnimation(wrapperAnimation);
@@ -2,7 +2,6 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { createAnimation } from "../../../utils/animation/animation";
5
- import { win } from "../../../utils/browser/index";
6
5
  import { getElementRoot } from "../../../utils/helpers";
7
6
  /**
8
7
  * MD Toast Enter Animation
@@ -10,47 +9,9 @@ import { getElementRoot } from "../../../utils/helpers";
10
9
  export const mdEnterAnimation = (baseEl, opts) => {
11
10
  const baseAnimation = createAnimation();
12
11
  const wrapperAnimation = createAnimation();
13
- const { position, positionAnchor } = opts;
12
+ const { position, top, bottom } = opts;
14
13
  const root = getElementRoot(baseEl);
15
14
  const wrapperEl = root.querySelector('.toast-wrapper');
16
- /**
17
- * Start with a predefined offset from the edge the toast will be
18
- * positioned relative to. By default, this will be the top edge
19
- * of the screen for position="top", or the bottom edge for
20
- * position="bottom".
21
- */
22
- let offset = 8;
23
- let top, bottom;
24
- /**
25
- * If positionAnchor is defined, add in the distance from the target
26
- * screen edge to the target anchor edge. For position="top", the
27
- * bottom anchor edge is targeted. For position="bottom", the top
28
- * anchor edge is targeted.
29
- */
30
- if (positionAnchor && win) {
31
- const box = positionAnchor.getBoundingClientRect();
32
- if (position === 'top') {
33
- offset += box.bottom;
34
- }
35
- else if (position === 'bottom') {
36
- /**
37
- * Just box.top is the distance from the top edge of the screen
38
- * to the top edge of the anchor. We want to calculate from the
39
- * bottom edge of the screen instead.
40
- */
41
- offset += win.innerHeight - box.top;
42
- }
43
- /**
44
- * We don't include safe area here because that should already be
45
- * accounted for when checking the position of the anchor.
46
- */
47
- top = `${offset}px`;
48
- bottom = `${offset}px`;
49
- }
50
- else {
51
- top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;
52
- bottom = `calc(${offset}px + var(--ion-safe-area-bottom, 0px))`;
53
- }
54
15
  wrapperAnimation.addElement(wrapperEl);
55
16
  switch (position) {
56
17
  case 'top':