@ionic/core 7.3.3-dev.11693454416.1cb6a7ec → 7.3.3-dev.11693533652.1969cc78

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 (142) hide show
  1. package/components/action-sheet.js +8 -16
  2. package/components/alert.js +8 -16
  3. package/components/framework-delegate.js +21 -2
  4. package/components/ion-loading.js +8 -16
  5. package/components/ion-modal.js +21 -29
  6. package/components/ion-nav-link.js +1 -7
  7. package/components/ion-picker.js +8 -16
  8. package/components/ion-router-outlet.js +1 -0
  9. package/components/ion-toast.js +8 -16
  10. package/components/lock-controller.js +38 -0
  11. package/components/popover.js +15 -29
  12. package/dist/cjs/{framework-delegate-e8174951.js → framework-delegate-c0873a6f.js} +21 -2
  13. package/dist/cjs/index.cjs.js +2 -2
  14. package/dist/cjs/ion-action-sheet.cjs.entry.js +10 -18
  15. package/dist/cjs/ion-alert.cjs.entry.js +10 -18
  16. package/dist/cjs/ion-app_8.cjs.entry.js +2 -1
  17. package/dist/cjs/ion-datetime_3.cjs.entry.js +10 -18
  18. package/dist/cjs/ion-loading.cjs.entry.js +10 -18
  19. package/dist/cjs/ion-menu_3.cjs.entry.js +2 -2
  20. package/dist/cjs/ion-modal.cjs.entry.js +23 -31
  21. package/dist/cjs/ion-nav_2.cjs.entry.js +2 -8
  22. package/dist/cjs/ion-popover.cjs.entry.js +17 -31
  23. package/dist/cjs/ion-select_3.cjs.entry.js +2 -2
  24. package/dist/cjs/ion-tab_2.cjs.entry.js +1 -1
  25. package/dist/cjs/ion-toast.cjs.entry.js +10 -18
  26. package/dist/cjs/lock-controller-4ae2eb59.js +40 -0
  27. package/dist/cjs/{overlays-0ea06fcb.js → overlays-5c1706ed.js} +1 -1
  28. package/dist/collection/components/action-sheet/action-sheet.js +8 -16
  29. package/dist/collection/components/alert/alert.js +8 -16
  30. package/dist/collection/components/datetime/test/presentation/datetime.e2e.js +48 -17
  31. package/dist/collection/components/loading/loading.js +8 -16
  32. package/dist/collection/components/modal/modal.js +21 -29
  33. package/dist/collection/components/nav-link/nav-link.js +1 -7
  34. package/dist/collection/components/picker/picker.js +8 -16
  35. package/dist/collection/components/popover/popover.js +15 -29
  36. package/dist/collection/components/popover/test/async/popover.e2e.js +42 -0
  37. package/dist/collection/components/router-outlet/router-outlet.js +1 -0
  38. package/dist/collection/components/textarea/test/bottom-content/textarea.e2e.js +6 -6
  39. package/dist/collection/components/textarea/test/card/textarea.e2e.js +1 -1
  40. package/dist/collection/components/textarea/test/color/textarea.e2e.js +15 -15
  41. package/dist/collection/components/textarea/test/fill/textarea.e2e.js +10 -10
  42. package/dist/collection/components/textarea/test/highlight/textarea.e2e.js +9 -9
  43. package/dist/collection/components/textarea/test/item/textarea.e2e.js +2 -2
  44. package/dist/collection/components/textarea/test/label-placement/textarea.e2e.js +21 -21
  45. package/dist/collection/components/textarea/test/legacy/autogrow/textarea.e2e.js +5 -5
  46. package/dist/collection/components/textarea/test/states/textarea.e2e.js +2 -2
  47. package/dist/collection/components/toast/toast.js +8 -16
  48. package/dist/collection/utils/framework-delegate.js +21 -2
  49. package/dist/collection/utils/lock-controller.js +36 -0
  50. package/dist/docs.json +1 -1
  51. package/dist/esm/{framework-delegate-2b76c681.js → framework-delegate-aa433dea.js} +21 -2
  52. package/dist/esm/index.js +2 -2
  53. package/dist/esm/ion-action-sheet.entry.js +10 -18
  54. package/dist/esm/ion-alert.entry.js +10 -18
  55. package/dist/esm/ion-app_8.entry.js +2 -1
  56. package/dist/esm/ion-datetime_3.entry.js +10 -18
  57. package/dist/esm/ion-loading.entry.js +10 -18
  58. package/dist/esm/ion-menu_3.entry.js +2 -2
  59. package/dist/esm/ion-modal.entry.js +23 -31
  60. package/dist/esm/ion-nav_2.entry.js +2 -8
  61. package/dist/esm/ion-popover.entry.js +17 -31
  62. package/dist/esm/ion-select_3.entry.js +2 -2
  63. package/dist/esm/ion-tab_2.entry.js +1 -1
  64. package/dist/esm/ion-toast.entry.js +10 -18
  65. package/dist/esm/lock-controller-e8c6c051.js +38 -0
  66. package/dist/esm/{overlays-c3387ec0.js → overlays-e1dadfe6.js} +1 -1
  67. package/dist/esm-es5/framework-delegate-aa433dea.js +4 -0
  68. package/dist/esm-es5/index.js +1 -1
  69. package/dist/esm-es5/ion-action-sheet.entry.js +1 -1
  70. package/dist/esm-es5/ion-alert.entry.js +1 -1
  71. package/dist/esm-es5/ion-app_8.entry.js +1 -1
  72. package/dist/esm-es5/ion-datetime_3.entry.js +1 -1
  73. package/dist/esm-es5/ion-loading.entry.js +1 -1
  74. package/dist/esm-es5/ion-menu_3.entry.js +1 -1
  75. package/dist/esm-es5/ion-modal.entry.js +1 -1
  76. package/dist/esm-es5/ion-nav_2.entry.js +1 -1
  77. package/dist/esm-es5/ion-popover.entry.js +1 -1
  78. package/dist/esm-es5/ion-select_3.entry.js +1 -1
  79. package/dist/esm-es5/ion-tab_2.entry.js +1 -1
  80. package/dist/esm-es5/ion-toast.entry.js +1 -1
  81. package/dist/esm-es5/lock-controller-e8c6c051.js +4 -0
  82. package/dist/esm-es5/{overlays-c3387ec0.js → overlays-e1dadfe6.js} +1 -1
  83. package/dist/ionic/index.esm.js +1 -1
  84. package/dist/ionic/ionic.esm.js +1 -1
  85. package/dist/ionic/{p-958bde76.entry.js → p-091cd81e.entry.js} +1 -1
  86. package/dist/ionic/{p-c32e09fe.system.entry.js → p-0b68c41d.system.entry.js} +1 -1
  87. package/dist/ionic/p-0cc2bf5a.system.entry.js +4 -0
  88. package/dist/ionic/p-208f640c.entry.js +4 -0
  89. package/dist/ionic/p-2539e967.system.entry.js +4 -0
  90. package/dist/ionic/{p-74bd26b8.system.entry.js → p-28f5715a.system.entry.js} +1 -1
  91. package/dist/ionic/p-38dd9aec.entry.js +4 -0
  92. package/dist/ionic/p-3ff018da.entry.js +4 -0
  93. package/dist/ionic/{p-8074bdd7.system.entry.js → p-45ee211b.system.entry.js} +1 -1
  94. package/dist/ionic/p-5009cb46.entry.js +4 -0
  95. package/dist/ionic/p-53b2a46f.js +4 -0
  96. package/dist/ionic/{p-5ef91692.js → p-582dc7c1.js} +1 -1
  97. package/dist/ionic/{p-c0d1e5a1.entry.js → p-5c17f888.entry.js} +1 -1
  98. package/dist/ionic/{p-8d9475b5.system.js → p-613d4042.system.js} +1 -1
  99. package/dist/ionic/p-647d15d4.system.entry.js +4 -0
  100. package/dist/ionic/p-653f2fc6.entry.js +4 -0
  101. package/dist/ionic/p-721933ff.system.js +1 -1
  102. package/dist/ionic/p-772f6c84.system.js +4 -0
  103. package/dist/ionic/p-87383d00.system.entry.js +4 -0
  104. package/dist/ionic/{p-db46fc66.system.js → p-8843b046.system.js} +1 -1
  105. package/dist/ionic/p-93e47822.entry.js +4 -0
  106. package/dist/ionic/{p-0e6ca480.entry.js → p-98ebc376.entry.js} +1 -1
  107. package/dist/ionic/p-af0bc751.system.entry.js +4 -0
  108. package/dist/ionic/p-b0e9e0b8.system.entry.js +4 -0
  109. package/dist/ionic/p-b287ab05.js +4 -0
  110. package/dist/ionic/{p-81b46714.system.js → p-b32742f3.system.js} +1 -1
  111. package/dist/ionic/p-b3394798.system.entry.js +4 -0
  112. package/dist/ionic/{p-20087c90.entry.js → p-c759a704.entry.js} +1 -1
  113. package/dist/ionic/{p-6b9bf14c.system.entry.js → p-d1d3b740.system.entry.js} +1 -1
  114. package/dist/ionic/{p-8fa43732.system.entry.js → p-d693e765.system.entry.js} +1 -1
  115. package/dist/ionic/{p-5c96dc4e.entry.js → p-f7faa32d.entry.js} +1 -1
  116. package/dist/ionic/p-fabb7294.entry.js +4 -0
  117. package/dist/types/components/action-sheet/action-sheet.d.ts +1 -1
  118. package/dist/types/components/alert/alert.d.ts +1 -1
  119. package/dist/types/components/loading/loading.d.ts +1 -1
  120. package/dist/types/components/modal/modal.d.ts +1 -1
  121. package/dist/types/components/picker/picker.d.ts +1 -1
  122. package/dist/types/components/popover/popover.d.ts +1 -1
  123. package/dist/types/components/toast/toast.d.ts +1 -1
  124. package/dist/types/utils/lock-controller.d.ts +10 -0
  125. package/hydrate/index.js +126 -147
  126. package/package.json +1 -1
  127. package/dist/esm-es5/framework-delegate-2b76c681.js +0 -4
  128. package/dist/ionic/p-06f1f560.system.entry.js +0 -4
  129. package/dist/ionic/p-2936ec7f.system.entry.js +0 -4
  130. package/dist/ionic/p-29b3e70a.entry.js +0 -4
  131. package/dist/ionic/p-2bc49e5d.system.entry.js +0 -4
  132. package/dist/ionic/p-2da3e015.system.entry.js +0 -4
  133. package/dist/ionic/p-423d5a4d.entry.js +0 -4
  134. package/dist/ionic/p-564aaba3.entry.js +0 -4
  135. package/dist/ionic/p-61769039.entry.js +0 -4
  136. package/dist/ionic/p-74723f41.entry.js +0 -4
  137. package/dist/ionic/p-9b3c3564.entry.js +0 -4
  138. package/dist/ionic/p-b34c1ae1.js +0 -4
  139. package/dist/ionic/p-ccea9adc.system.entry.js +0 -4
  140. package/dist/ionic/p-d5db0834.system.entry.js +0 -4
  141. package/dist/ionic/p-d8538837.entry.js +0 -4
  142. package/dist/ionic/p-dd0b6889.system.entry.js +0 -4
@@ -5,6 +5,7 @@ import { Host, h, writeTask } from "@stencil/core";
5
5
  import { findIonContent, printIonContentErrorMsg } from "../../utils/content/index";
6
6
  import { CoreDelegate, attachComponent, detachComponent } from "../../utils/framework-delegate";
7
7
  import { raf, inheritAttributes, hasLazyBuild } from "../../utils/helpers";
8
+ import { createLockController } from "../../utils/lock-controller";
8
9
  import { printIonWarning } from "../../utils/logging/index";
9
10
  import { Style as StatusBarStyle, StatusBar } from "../../utils/native/status-bar";
10
11
  import { GESTURE, BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present, createTriggerController, setOverlayId, } from "../../utils/overlays";
@@ -32,6 +33,7 @@ import { setCardStatusBarDark, setCardStatusBarDefault } from "./utils";
32
33
  */
33
34
  export class Modal {
34
35
  constructor() {
36
+ this.lockController = createLockController();
35
37
  this.triggerController = createTriggerController();
36
38
  this.coreDelegate = CoreDelegate();
37
39
  this.isSheetModal = false;
@@ -196,29 +198,25 @@ export class Modal {
196
198
  * Present the modal overlay after it has been created.
197
199
  */
198
200
  async present() {
201
+ const unlock = await this.lockController.lock();
199
202
  if (this.presented) {
203
+ unlock();
200
204
  return;
201
205
  }
202
206
  const { presentingElement, el } = this;
203
- /**
204
- * When using an inline modal
205
- * and dismissing a modal it is possible to
206
- * quickly present the modal while it is
207
- * dismissing. We need to await any current
208
- * transition to allow the dismiss to finish
209
- * before presenting again.
210
- */
211
- if (this.currentTransition !== undefined) {
212
- await this.currentTransition;
213
- }
214
207
  /**
215
208
  * If the modal is presented multiple times (inline modals), we
216
209
  * need to reset the current breakpoint to the initial breakpoint.
217
210
  */
218
211
  this.currentBreakpoint = this.initialBreakpoint;
219
212
  const { inline, delegate } = this.getDelegate(true);
220
- this.usersElement = await attachComponent(delegate, el, this.component, ['ion-page'], this.componentProps, inline);
213
+ /**
214
+ * Emit ionMount so JS Frameworks have an opportunity
215
+ * to add the child component to the DOM. The child
216
+ * component will be assigned to this.usersElement below.
217
+ */
221
218
  this.ionMount.emit();
219
+ this.usersElement = await attachComponent(delegate, el, this.component, ['ion-page'], this.componentProps, inline);
222
220
  /**
223
221
  * When using the lazy loaded build of Stencil, we need to wait
224
222
  * for every Stencil component instance to be ready before presenting
@@ -242,7 +240,7 @@ export class Modal {
242
240
  await waitForMount();
243
241
  }
244
242
  writeTask(() => this.el.classList.add('show-modal'));
245
- this.currentTransition = present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, {
243
+ await present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, {
246
244
  presentingEl: presentingElement,
247
245
  currentBreakpoint: this.initialBreakpoint,
248
246
  backdropBreakpoint: this.backdropBreakpoint,
@@ -289,14 +287,13 @@ export class Modal {
289
287
  this.statusBarStyle = await StatusBar.getStyle();
290
288
  setCardStatusBarDark();
291
289
  }
292
- await this.currentTransition;
293
290
  if (this.isSheetModal) {
294
291
  this.initSheetGesture();
295
292
  }
296
293
  else if (hasCardModal) {
297
294
  this.initSwipeToClose();
298
295
  }
299
- this.currentTransition = undefined;
296
+ unlock();
300
297
  }
301
298
  initSwipeToClose() {
302
299
  var _a;
@@ -386,12 +383,19 @@ export class Modal {
386
383
  if (this.gestureAnimationDismissing && role !== GESTURE) {
387
384
  return false;
388
385
  }
386
+ /**
387
+ * Because the canDismiss check below is async,
388
+ * we need to claim a lock before the check happens,
389
+ * in case the dismiss transition does run.
390
+ */
391
+ const unlock = await this.lockController.lock();
389
392
  /**
390
393
  * If a canDismiss handler is responsible
391
394
  * for calling the dismiss method, we should
392
395
  * not run the canDismiss check again.
393
396
  */
394
397
  if (role !== 'handler' && !(await this.checkCanDismiss(data, role))) {
398
+ unlock();
395
399
  return false;
396
400
  }
397
401
  const { presentingElement } = this;
@@ -409,24 +413,12 @@ export class Modal {
409
413
  window.removeEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
410
414
  this.keyboardOpenCallback = undefined;
411
415
  }
412
- /**
413
- * When using an inline modal
414
- * and presenting a modal it is possible to
415
- * quickly dismiss the modal while it is
416
- * presenting. We need to await any current
417
- * transition to allow the present to finish
418
- * before dismissing again.
419
- */
420
- if (this.currentTransition !== undefined) {
421
- await this.currentTransition;
422
- }
423
416
  const enteringAnimation = activeAnimations.get(this) || [];
424
- this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, {
417
+ const dismissed = await dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, {
425
418
  presentingEl: presentingElement,
426
419
  currentBreakpoint: (_a = this.currentBreakpoint) !== null && _a !== void 0 ? _a : this.initialBreakpoint,
427
420
  backdropBreakpoint: this.backdropBreakpoint,
428
421
  });
429
- const dismissed = await this.currentTransition;
430
422
  if (dismissed) {
431
423
  const { delegate } = this.getDelegate();
432
424
  await detachComponent(delegate, this.usersElement);
@@ -440,8 +432,8 @@ export class Modal {
440
432
  enteringAnimation.forEach((ani) => ani.destroy());
441
433
  }
442
434
  this.currentBreakpoint = undefined;
443
- this.currentTransition = undefined;
444
435
  this.animation = undefined;
436
+ unlock();
445
437
  return dismissed;
446
438
  }
447
439
  /**
@@ -6,13 +6,7 @@ import { navLink } from "./nav-link-utils";
6
6
  export class NavLink {
7
7
  constructor() {
8
8
  this.onClick = () => {
9
- const { el } = this;
10
- const button = el.querySelector('button,ion-button,a');
11
- if (button === null || button === void 0 ? void 0 : button.hasAttribute('disabled')) {
12
- // Do not navigate if the button is disabled
13
- return;
14
- }
15
- return navLink(el, this.routerDirection, this.component, this.componentProps, this.routerAnimation);
9
+ return navLink(this.el, this.routerDirection, this.component, this.componentProps, this.routerAnimation);
16
10
  };
17
11
  this.component = undefined;
18
12
  this.componentProps = undefined;
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { Host, h } from "@stencil/core";
5
5
  import { raf } from "../../utils/helpers";
6
+ import { createLockController } from "../../utils/lock-controller";
6
7
  import { createDelegateController, createTriggerController, BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall, setOverlayId, } from "../../utils/overlays";
7
8
  import { getClassMap } from "../../utils/theme";
8
9
  import { getIonMode } from "../../global/ionic-global";
@@ -15,6 +16,7 @@ import { iosLeaveAnimation } from "./animations/ios.leave";
15
16
  export class Picker {
16
17
  constructor() {
17
18
  this.delegateController = createDelegateController(this);
19
+ this.lockController = createLockController();
18
20
  this.triggerController = createTriggerController();
19
21
  this.onBackdropTap = () => {
20
22
  this.dismiss(undefined, BACKDROP);
@@ -81,24 +83,13 @@ export class Picker {
81
83
  * Present the picker overlay after it has been created.
82
84
  */
83
85
  async present() {
84
- /**
85
- * When using an inline picker
86
- * and dismissing an picker it is possible to
87
- * quickly present the picker while it is
88
- * dismissing. We need to await any current
89
- * transition to allow the dismiss to finish
90
- * before presenting again.
91
- */
92
- if (this.currentTransition !== undefined) {
93
- await this.currentTransition;
94
- }
86
+ const unlock = await this.lockController.lock();
95
87
  await this.delegateController.attachViewToDom();
96
- this.currentTransition = present(this, 'pickerEnter', iosEnterAnimation, iosEnterAnimation, undefined);
97
- await this.currentTransition;
98
- this.currentTransition = undefined;
88
+ await present(this, 'pickerEnter', iosEnterAnimation, iosEnterAnimation, undefined);
99
89
  if (this.duration > 0) {
100
90
  this.durationTimeout = setTimeout(() => this.dismiss(), this.duration);
101
91
  }
92
+ unlock();
102
93
  }
103
94
  /**
104
95
  * Dismiss the picker overlay after it has been presented.
@@ -110,14 +101,15 @@ export class Picker {
110
101
  * Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
111
102
  */
112
103
  async dismiss(data, role) {
104
+ const unlock = await this.lockController.lock();
113
105
  if (this.durationTimeout) {
114
106
  clearTimeout(this.durationTimeout);
115
107
  }
116
- this.currentTransition = dismiss(this, data, role, 'pickerLeave', iosLeaveAnimation, iosLeaveAnimation);
117
- const dismissed = await this.currentTransition;
108
+ const dismissed = await dismiss(this, data, role, 'pickerLeave', iosLeaveAnimation, iosLeaveAnimation);
118
109
  if (dismissed) {
119
110
  this.delegateController.removeViewFromDom();
120
111
  }
112
+ unlock();
121
113
  return dismissed;
122
114
  }
123
115
  /**
@@ -4,6 +4,7 @@
4
4
  import { Host, h } from "@stencil/core";
5
5
  import { CoreDelegate, attachComponent, detachComponent } from "../../utils/framework-delegate";
6
6
  import { addEventListener, raf, hasLazyBuild } from "../../utils/helpers";
7
+ import { createLockController } from "../../utils/lock-controller";
7
8
  import { printIonWarning } from "../../utils/logging/index";
8
9
  import { BACKDROP, dismiss, eventMethod, focusFirstDescendant, prepareOverlay, present, setOverlayId, } from "../../utils/overlays";
9
10
  import { isPlatform } from "../../utils/platform";
@@ -29,6 +30,7 @@ export class Popover {
29
30
  constructor() {
30
31
  this.parentPopover = null;
31
32
  this.coreDelegate = CoreDelegate();
33
+ this.lockController = createLockController();
32
34
  this.inline = false;
33
35
  this.focusDescendantOnPresent = false;
34
36
  this.onBackdropTap = () => {
@@ -202,28 +204,24 @@ export class Popover {
202
204
  * was dispatched.
203
205
  */
204
206
  async present(event) {
207
+ const unlock = await this.lockController.lock();
205
208
  if (this.presented) {
209
+ unlock();
206
210
  return;
207
211
  }
208
- /**
209
- * When using an inline popover
210
- * and dismissing a popover it is possible to
211
- * quickly present the popover while it is
212
- * dismissing. We need to await any current
213
- * transition to allow the dismiss to finish
214
- * before presenting again.
215
- */
216
- if (this.currentTransition !== undefined) {
217
- await this.currentTransition;
218
- }
219
212
  const { el } = this;
220
213
  const { inline, delegate } = this.getDelegate(true);
214
+ /**
215
+ * Emit ionMount so JS Frameworks have an opportunity
216
+ * to add the child component to the DOM. The child
217
+ * component will be assigned to this.usersElement below.
218
+ */
219
+ this.ionMount.emit();
221
220
  this.usersElement = await attachComponent(delegate, el, this.component, ['popover-viewport'], this.componentProps, inline);
222
221
  if (!this.keyboardEvents) {
223
222
  this.configureKeyboardInteraction();
224
223
  }
225
224
  this.configureDismissInteraction();
226
- this.ionMount.emit();
227
225
  /**
228
226
  * When using the lazy loaded build of Stencil, we need to wait
229
227
  * for every Stencil component instance to be ready before presenting
@@ -246,7 +244,7 @@ export class Popover {
246
244
  else if (!this.keepContentsMounted) {
247
245
  await waitForMount();
248
246
  }
249
- this.currentTransition = present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, {
247
+ await present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, {
250
248
  event: event || this.event,
251
249
  size: this.size,
252
250
  trigger: this.triggerEl,
@@ -254,8 +252,6 @@ export class Popover {
254
252
  side: this.side,
255
253
  align: this.alignment,
256
254
  });
257
- await this.currentTransition;
258
- this.currentTransition = undefined;
259
255
  /**
260
256
  * If popover is nested and was
261
257
  * presented using the "Right" arrow key,
@@ -265,6 +261,7 @@ export class Popover {
265
261
  if (this.focusDescendantOnPresent) {
266
262
  focusFirstDescendant(this.el, this.el);
267
263
  }
264
+ unlock();
268
265
  }
269
266
  /**
270
267
  * Dismiss the popover overlay after it has been presented.
@@ -275,23 +272,12 @@ export class Popover {
275
272
  * a parent popover if this popover is nested. Defaults to `true`.
276
273
  */
277
274
  async dismiss(data, role, dismissParentPopover = true) {
278
- /**
279
- * When using an inline popover
280
- * and presenting a popover it is possible to
281
- * quickly dismiss the popover while it is
282
- * presenting. We need to await any current
283
- * transition to allow the present to finish
284
- * before dismissing again.
285
- */
286
- if (this.currentTransition !== undefined) {
287
- await this.currentTransition;
288
- }
275
+ const unlock = await this.lockController.lock();
289
276
  const { destroyKeyboardInteraction, destroyDismissInteraction } = this;
290
277
  if (dismissParentPopover && this.parentPopover) {
291
278
  this.parentPopover.dismiss(data, role, dismissParentPopover);
292
279
  }
293
- this.currentTransition = dismiss(this, data, role, 'popoverLeave', iosLeaveAnimation, mdLeaveAnimation, this.event);
294
- const shouldDismiss = await this.currentTransition;
280
+ const shouldDismiss = await dismiss(this, data, role, 'popoverLeave', iosLeaveAnimation, mdLeaveAnimation, this.event);
295
281
  if (shouldDismiss) {
296
282
  if (destroyKeyboardInteraction) {
297
283
  destroyKeyboardInteraction();
@@ -309,7 +295,7 @@ export class Popover {
309
295
  const { delegate } = this.getDelegate();
310
296
  await detachComponent(delegate, this.usersElement);
311
297
  }
312
- this.currentTransition = undefined;
298
+ unlock();
313
299
  return shouldDismiss;
314
300
  }
315
301
  /**
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ import { expect } from "@playwright/test";
5
+ import { configs, test } from "../../../../utils/test/playwright/index";
6
+ /**
7
+ * This behavior does not vary across modes/directions
8
+ */
9
+ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
10
+ test.describe(title('popover: alignment with async component'), async () => {
11
+ test('should align popover centered with button when component is added async', async ({ page }) => {
12
+ await page.goto('/src/components/popover/test/async', config);
13
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
14
+ const button = page.locator('#button');
15
+ await button.click();
16
+ await ionPopoverDidPresent.next();
17
+ await expect(page).toHaveScreenshot(screenshot(`popover-async`));
18
+ });
19
+ /**
20
+ * Framework delegate should fall back to returning the host
21
+ * component when no child content is passed otherwise
22
+ * the overlay will get stuck when trying to re-present.
23
+ */
24
+ test('should open popover even if nothing was passed', async ({ page }) => {
25
+ await page.setContent(`
26
+ <ion-popover></ion-popover>
27
+ `, config);
28
+ const popover = page.locator('ion-popover');
29
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
30
+ const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss');
31
+ await popover.evaluate((el) => el.present());
32
+ await ionPopoverDidPresent.next();
33
+ await expect(popover).toBeVisible();
34
+ await popover.evaluate((el) => el.dismiss());
35
+ await ionPopoverDidDismiss.next();
36
+ await expect(popover).toBeHidden();
37
+ await popover.evaluate((el) => el.present());
38
+ await ionPopoverDidPresent.next();
39
+ await expect(popover).toBeVisible();
40
+ });
41
+ });
42
+ });
@@ -174,6 +174,7 @@ export class RouterOutlet {
174
174
  this.ionNavDidChange.emit();
175
175
  return true;
176
176
  }
177
+ // TODO: FW-5048 - Remove this code in favor of using lock controller from utils
177
178
  async lock() {
178
179
  const p = this.waitPromise;
179
180
  let resolve;
@@ -41,7 +41,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
41
41
  <ion-textarea class="ion-invalid ion-touched custom-textarea" label="my label" error-text="my error"></ion-textarea>
42
42
  `, config);
43
43
  const errorText = page.locator('ion-textarea .error-text');
44
- expect(await errorText.screenshot()).toMatchSnapshot(screenshot(`textarea-error-custom-color`));
44
+ await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-custom-color`));
45
45
  });
46
46
  });
47
47
  test.describe('textarea: hint text rendering', () => {
@@ -49,12 +49,12 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
49
49
  test('should not have visual regressions when rendering helper text', async ({ page }) => {
50
50
  await page.setContent(`<ion-textarea helper-text="my helper" label="my textarea"></ion-textarea>`, config);
51
51
  const bottomEl = page.locator('ion-textarea .textarea-bottom');
52
- expect(await bottomEl.screenshot()).toMatchSnapshot(screenshot(`textarea-bottom-content-helper`));
52
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-helper`));
53
53
  });
54
54
  test('should not have visual regressions when rendering error text', async ({ page }) => {
55
55
  await page.setContent(`<ion-textarea class="ion-invalid ion-touched" error-text="my helper" label="my textarea"></ion-textarea>`, config);
56
56
  const bottomEl = page.locator('ion-textarea .textarea-bottom');
57
- expect(await bottomEl.screenshot()).toMatchSnapshot(screenshot(`textarea-bottom-content-error`));
57
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-error`));
58
58
  });
59
59
  });
60
60
  });
@@ -102,17 +102,17 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
102
102
  test('should not have visual regressions when rendering counter', async ({ page }) => {
103
103
  await page.setContent(`<ion-textarea counter="true" maxlength="20" label="my textarea"></ion-textarea>`, config);
104
104
  const bottomEl = page.locator('ion-textarea .textarea-bottom');
105
- expect(await bottomEl.screenshot()).toMatchSnapshot(screenshot(`textarea-bottom-content-counter`));
105
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter`));
106
106
  });
107
107
  test('should not have visual regressions when rendering counter with helper text', async ({ page }) => {
108
108
  await page.setContent(`<ion-textarea label="my textarea" counter="true" maxlength="20" helper-text="my helper"></ion-textarea>`, config);
109
109
  const bottomEl = page.locator('ion-textarea .textarea-bottom');
110
- expect(await bottomEl.screenshot()).toMatchSnapshot(screenshot(`textarea-bottom-content-counter-helper-text`));
110
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter-helper-text`));
111
111
  });
112
112
  test('should not have visual regressions when rendering counter with error text', async ({ page }) => {
113
113
  await page.setContent(`<ion-textarea class="ion-invalid ion-touched" label="my textarea" counter="true" maxlength="20" error-text="my error"></ion-textarea>`, config);
114
114
  const bottomEl = page.locator('ion-textarea .textarea-bottom');
115
- expect(await bottomEl.screenshot()).toMatchSnapshot(screenshot(`textarea-bottom-content-counter-error-text`));
115
+ await expect(bottomEl).toHaveScreenshot(screenshot(`textarea-bottom-content-counter-error-text`));
116
116
  });
117
117
  });
118
118
  });
@@ -16,7 +16,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
16
16
  </ion-card>
17
17
  `, config);
18
18
  const card = page.locator('ion-card');
19
- expect(await card.screenshot()).toMatchSnapshot(screenshot(`textarea-card`));
19
+ await expect(card).toHaveScreenshot(screenshot(`textarea-card`));
20
20
  });
21
21
  });
22
22
  });
@@ -22,35 +22,35 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
22
22
  <ion-textarea class="has-focus" color="danger" label="Email" label-placement="start" value="hi@ionic.io"></ion-textarea>
23
23
  `, config);
24
24
  const textarea = page.locator('ion-textarea');
25
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-no-fill-color-start`));
25
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-no-fill-color-start`));
26
26
  });
27
27
  test('should set label and highlight color on focus with end label placement', async ({ page }) => {
28
28
  await page.setContent(`
29
29
  <ion-textarea class="has-focus" color="danger" label="Email" label-placement="end" value="hi@ionic.io"></ion-textarea>
30
30
  `, config);
31
31
  const textarea = page.locator('ion-textarea');
32
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-no-fill-color-end`));
32
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-no-fill-color-end`));
33
33
  });
34
34
  test('should set label and highlight color on focus with fixed label placement', async ({ page }) => {
35
35
  await page.setContent(`
36
36
  <ion-textarea class="has-focus" color="danger" label="Email" label-placement="fixed" value="hi@ionic.io"></ion-textarea>
37
37
  `, config);
38
38
  const textarea = page.locator('ion-textarea');
39
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-no-fill-color-fixed`));
39
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-no-fill-color-fixed`));
40
40
  });
41
41
  test('should set label and highlight color on focus with floating label placement', async ({ page }) => {
42
42
  await page.setContent(`
43
43
  <ion-textarea class="has-focus" color="danger" label="Email" label-placement="floating" value="hi@ionic.io"></ion-textarea>
44
44
  `, config);
45
45
  const textarea = page.locator('ion-textarea');
46
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-no-fill-color-floating`));
46
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-no-fill-color-floating`));
47
47
  });
48
48
  test('should set label and highlight color on focus with stacked label placement', async ({ page }) => {
49
49
  await page.setContent(`
50
50
  <ion-textarea class="has-focus" color="danger" label="Email" label-placement="stacked" value="hi@ionic.io"></ion-textarea>
51
51
  `, config);
52
52
  const textarea = page.locator('ion-textarea');
53
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-no-fill-color-stacked`));
53
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-no-fill-color-stacked`));
54
54
  });
55
55
  });
56
56
  test.describe('textarea: fill solid', () => {
@@ -59,35 +59,35 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
59
59
  <ion-textarea fill="solid" class="has-focus" color="danger" label="Email" label-placement="start" value="hi@ionic.io"></ion-textarea>
60
60
  `, config);
61
61
  const textarea = page.locator('ion-textarea');
62
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-solid-color-start`));
62
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-solid-color-start`));
63
63
  });
64
64
  test('should set label and highlight color on focus with end label placement', async ({ page }) => {
65
65
  await page.setContent(`
66
66
  <ion-textarea fill="solid" class="has-focus" color="danger" label="Email" label-placement="end" value="hi@ionic.io"></ion-textarea>
67
67
  `, config);
68
68
  const textarea = page.locator('ion-textarea');
69
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-solid-color-end`));
69
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-solid-color-end`));
70
70
  });
71
71
  test('should set label and highlight color on focus with fixed label placement', async ({ page }) => {
72
72
  await page.setContent(`
73
73
  <ion-textarea fill="solid" class="has-focus" color="danger" label="Email" label-placement="fixed" value="hi@ionic.io"></ion-textarea>
74
74
  `, config);
75
75
  const textarea = page.locator('ion-textarea');
76
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-solid-color-fixed`));
76
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-solid-color-fixed`));
77
77
  });
78
78
  test('should set label and highlight color on focus with floating label placement', async ({ page }) => {
79
79
  await page.setContent(`
80
80
  <ion-textarea fill="solid" class="has-focus" color="danger" label="Email" label-placement="floating" value="hi@ionic.io"></ion-textarea>
81
81
  `, config);
82
82
  const textarea = page.locator('ion-textarea');
83
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-solid-color-floating`));
83
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-solid-color-floating`));
84
84
  });
85
85
  test('should set label and highlight color on focus with stacked label placement', async ({ page }) => {
86
86
  await page.setContent(`
87
87
  <ion-textarea fill="solid" class="has-focus" color="danger" label="Email" label-placement="stacked" value="hi@ionic.io"></ion-textarea>
88
88
  `, config);
89
89
  const textarea = page.locator('ion-textarea');
90
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-solid-color-stacked`));
90
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-solid-color-stacked`));
91
91
  });
92
92
  });
93
93
  test.describe('textarea: fill outline', () => {
@@ -96,35 +96,35 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
96
96
  <ion-textarea fill="outline" class="has-focus" color="danger" label="Email" label-placement="start" value="hi@ionic.io"></ion-textarea>
97
97
  `, config);
98
98
  const textarea = page.locator('ion-textarea');
99
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-outline-color-start`));
99
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-outline-color-start`));
100
100
  });
101
101
  test('should set label and highlight color on focus with end label placement', async ({ page }) => {
102
102
  await page.setContent(`
103
103
  <ion-textarea fill="outline" class="has-focus" color="danger" label="Email" label-placement="end" value="hi@ionic.io"></ion-textarea>
104
104
  `, config);
105
105
  const textarea = page.locator('ion-textarea');
106
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-outline-color-end`));
106
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-outline-color-end`));
107
107
  });
108
108
  test('should set label and highlight color on focus with fixed label placement', async ({ page }) => {
109
109
  await page.setContent(`
110
110
  <ion-textarea fill="outline" class="has-focus" color="danger" label="Email" label-placement="fixed" value="hi@ionic.io"></ion-textarea>
111
111
  `, config);
112
112
  const textarea = page.locator('ion-textarea');
113
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-outline-color-fixed`));
113
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-outline-color-fixed`));
114
114
  });
115
115
  test('should set label and highlight color on focus with floating label placement', async ({ page }) => {
116
116
  await page.setContent(`
117
117
  <ion-textarea fill="outline" class="has-focus" color="danger" label="Email" label-placement="floating" value="hi@ionic.io"></ion-textarea>
118
118
  `, config);
119
119
  const textarea = page.locator('ion-textarea');
120
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-outline-color-floating`));
120
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-outline-color-floating`));
121
121
  });
122
122
  test('should set label and highlight color on focus with stacked label placement', async ({ page }) => {
123
123
  await page.setContent(`
124
124
  <ion-textarea fill="outline" class="has-focus" color="danger" label="Email" label-placement="stacked" value="hi@ionic.io"></ion-textarea>
125
125
  `, config);
126
126
  const textarea = page.locator('ion-textarea');
127
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-outline-color-stacked`));
127
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-outline-color-stacked`));
128
128
  });
129
129
  });
130
130
  });
@@ -21,7 +21,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
21
21
  ></ion-textarea>
22
22
  `, config);
23
23
  const textarea = page.locator('ion-textarea');
24
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-solid`));
24
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-solid`));
25
25
  });
26
26
  test('should render correctly with floating label', async ({ page }) => {
27
27
  await page.setContent(`
@@ -36,7 +36,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
36
36
  ></ion-textarea>
37
37
  `, config);
38
38
  const textarea = page.locator('ion-textarea');
39
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-solid-label-floating`));
39
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-solid-label-floating`));
40
40
  });
41
41
  test('should not have visual regressions with shaped solid', async ({ page }) => {
42
42
  await page.setContent(`
@@ -51,7 +51,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
51
51
  ></ion-textarea>
52
52
  `, config);
53
53
  const textarea = page.locator('ion-textarea');
54
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-shaped-solid`));
54
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-shaped-solid`));
55
55
  });
56
56
  test('padding and border radius should be customizable', async ({ page }) => {
57
57
  await page.setContent(`
@@ -75,7 +75,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
75
75
  ></ion-textarea>
76
76
  `, config);
77
77
  const textarea = page.locator('ion-textarea');
78
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-shaped-solid-custom`));
78
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-shaped-solid-custom`));
79
79
  });
80
80
  });
81
81
  test.describe('textarea: fill outline', () => {
@@ -91,7 +91,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
91
91
  ></ion-textarea>
92
92
  `, config);
93
93
  const textarea = page.locator('ion-textarea');
94
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline`));
94
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-outline`));
95
95
  });
96
96
  test('should render correctly with floating label', async ({ page }) => {
97
97
  await page.setContent(`
@@ -106,7 +106,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
106
106
  ></ion-textarea>
107
107
  `, config);
108
108
  const textarea = page.locator('ion-textarea');
109
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-label-floating`));
109
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-outline-label-floating`));
110
110
  });
111
111
  test('should not have visual regressions with shaped outline', async ({ page }) => {
112
112
  await page.setContent(`
@@ -121,7 +121,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
121
121
  ></ion-textarea>
122
122
  `, config);
123
123
  const textarea = page.locator('ion-textarea');
124
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-shaped-outline`));
124
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-shaped-outline`));
125
125
  });
126
126
  test('padding and border radius should be customizable', async ({ page }) => {
127
127
  await page.setContent(`
@@ -145,7 +145,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
145
145
  ></ion-textarea>
146
146
  `, config);
147
147
  const textarea = page.locator('ion-textarea');
148
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-shaped-outline-custom`));
148
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-shaped-outline-custom`));
149
149
  });
150
150
  });
151
151
  });
@@ -179,7 +179,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
179
179
  </ion-textarea>
180
180
  `, config);
181
181
  const textarea = page.locator('ion-textarea');
182
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-slotted-label`));
182
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-outline-slotted-label`));
183
183
  });
184
184
  test('should render the notch correctly with a slotted label after the textarea was originally hidden', async ({ page, }) => {
185
185
  await page.setContent(`
@@ -199,7 +199,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
199
199
  `, config);
200
200
  const textarea = page.locator('ion-textarea');
201
201
  await textarea.evaluate((el) => el.style.removeProperty('display'));
202
- expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-hidden-slotted-label`));
202
+ await expect(textarea).toHaveScreenshot(screenshot(`textarea-fill-outline-hidden-slotted-label`));
203
203
  });
204
204
  });
205
205
  });