@ionic/core 8.6.4-nightly.20250708 → 8.6.4
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.
- package/components/modal.js +282 -9
- package/dist/cjs/ion-modal.cjs.entry.js +281 -8
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/modal/animations/ios.enter.js +2 -2
- package/dist/collection/components/modal/animations/ios.leave.js +2 -2
- package/dist/collection/components/modal/animations/ios.transition.js +162 -0
- package/dist/collection/components/modal/modal.js +140 -6
- package/dist/collection/utils/test/playwright/page/utils/set-content.js +20 -2
- package/dist/docs.json +9 -2
- package/dist/esm/ion-modal.entry.js +282 -9
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-5c88e4c6.entry.js +4 -0
- package/dist/types/components/modal/animations/ios.transition.d.ts +14 -0
- package/dist/types/components/modal/modal.d.ts +9 -0
- package/dist/types/utils/test/playwright/playwright-declarations.d.ts +5 -0
- package/hydrate/index.js +282 -9
- package/hydrate/index.mjs +282 -9
- package/package.json +1 -1
- package/dist/ionic/p-9e32212d.entry.js +0 -4
|
@@ -30,6 +30,24 @@ export const setContent = async (page, html, testInfo, options) => {
|
|
|
30
30
|
palette = options.palette;
|
|
31
31
|
}
|
|
32
32
|
const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL;
|
|
33
|
+
// The Ionic bundle is included locally by default unless the test
|
|
34
|
+
// config passes in the importIonicFromCDN option. This is useful
|
|
35
|
+
// when testing with the CDN version of Ionic.
|
|
36
|
+
let ionicCSSImports = `
|
|
37
|
+
<link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
|
|
38
|
+
`;
|
|
39
|
+
let ionicJSImports = `
|
|
40
|
+
<script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
|
|
41
|
+
`;
|
|
42
|
+
if (options === null || options === void 0 ? void 0 : options.importIonicFromCDN) {
|
|
43
|
+
ionicCSSImports = `
|
|
44
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
|
45
|
+
`;
|
|
46
|
+
ionicJSImports = `
|
|
47
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
|
48
|
+
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
33
51
|
const output = `
|
|
34
52
|
<!DOCTYPE html>
|
|
35
53
|
<html dir="${direction}" lang="en">
|
|
@@ -37,11 +55,11 @@ export const setContent = async (page, html, testInfo, options) => {
|
|
|
37
55
|
<title>Ionic Playwright Test</title>
|
|
38
56
|
<meta charset="UTF-8" />
|
|
39
57
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
|
40
|
-
|
|
58
|
+
${ionicCSSImports}
|
|
41
59
|
<link href="${baseUrl}/scripts/testing/styles.css" rel="stylesheet" />
|
|
42
60
|
${palette !== 'light' ? `<link href="${baseUrl}/css/palettes/${palette}.always.css" rel="stylesheet" />` : ''}
|
|
43
61
|
<script src="${baseUrl}/scripts/testing/scripts.js"></script>
|
|
44
|
-
|
|
62
|
+
${ionicJSImports}
|
|
45
63
|
<script>
|
|
46
64
|
window.Ionic = {
|
|
47
65
|
config: {
|
package/dist/docs.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2025-07-
|
|
2
|
+
"timestamp": "2025-07-09T20:11:49",
|
|
3
3
|
"compiler": {
|
|
4
4
|
"name": "@stencil/core",
|
|
5
5
|
"version": "4.33.1",
|
|
@@ -20226,7 +20226,14 @@
|
|
|
20226
20226
|
"docsTags": []
|
|
20227
20227
|
}
|
|
20228
20228
|
],
|
|
20229
|
-
"listeners": [
|
|
20229
|
+
"listeners": [
|
|
20230
|
+
{
|
|
20231
|
+
"event": "resize",
|
|
20232
|
+
"target": "window",
|
|
20233
|
+
"capture": false,
|
|
20234
|
+
"passive": true
|
|
20235
|
+
}
|
|
20236
|
+
],
|
|
20230
20237
|
"styles": [
|
|
20231
20238
|
{
|
|
20232
20239
|
"name": "--backdrop-opacity",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { r as registerInstance, d as createEvent,
|
|
4
|
+
import { r as registerInstance, d as createEvent, e as getIonMode, m as printIonWarning, w as writeTask, l as config, h, j as Host, k as getElement } from './index-B_U9CtaY.js';
|
|
5
5
|
import { f as findClosestIonContent, i as isIonContent, d as disableContentScrollY, r as resetContentScrollY, a as findIonContent, p as printIonContentErrorMsg } from './index-BlJTBdxG.js';
|
|
6
6
|
import { C as CoreDelegate, a as attachComponent, d as detachComponent } from './framework-delegate-DxcnWic_.js';
|
|
7
7
|
import { e as clamp, g as getElementRoot, r as raf, b as inheritAttributes, h as hasLazyBuild } from './helpers-1O4D2b7y.js';
|
|
@@ -587,7 +587,7 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
587
587
|
baseAnimation.addAnimation(contentAnimation);
|
|
588
588
|
}
|
|
589
589
|
if (presentingEl) {
|
|
590
|
-
const
|
|
590
|
+
const isPortrait = window.innerWidth < 768;
|
|
591
591
|
const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
|
|
592
592
|
const presentingElRoot = getElementRoot(presentingEl);
|
|
593
593
|
const presentingAnimation = createAnimation().beforeStyles({
|
|
@@ -596,7 +596,7 @@ const iosEnterAnimation = (baseEl, opts) => {
|
|
|
596
596
|
overflow: 'hidden',
|
|
597
597
|
});
|
|
598
598
|
const bodyEl = document.body;
|
|
599
|
-
if (
|
|
599
|
+
if (isPortrait) {
|
|
600
600
|
/**
|
|
601
601
|
* Fallback for browsers that does not support `max()` (ex: Firefox)
|
|
602
602
|
* No need to worry about statusbar padding since engines like Gecko
|
|
@@ -674,7 +674,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
|
|
|
674
674
|
.duration(duration)
|
|
675
675
|
.addAnimation(wrapperAnimation);
|
|
676
676
|
if (presentingEl) {
|
|
677
|
-
const
|
|
677
|
+
const isPortrait = window.innerWidth < 768;
|
|
678
678
|
const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
|
|
679
679
|
const presentingElRoot = getElementRoot(presentingEl);
|
|
680
680
|
const presentingAnimation = createAnimation()
|
|
@@ -692,7 +692,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
|
|
|
692
692
|
}
|
|
693
693
|
});
|
|
694
694
|
const bodyEl = document.body;
|
|
695
|
-
if (
|
|
695
|
+
if (isPortrait) {
|
|
696
696
|
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
|
697
697
|
const modalTransform = hasCardModal ? '-10px' : transformOffset;
|
|
698
698
|
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
@@ -739,6 +739,163 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
|
|
|
739
739
|
return baseAnimation;
|
|
740
740
|
};
|
|
741
741
|
|
|
742
|
+
/**
|
|
743
|
+
* Transition animation from portrait view to landscape view
|
|
744
|
+
* This handles the case where a card modal is open in portrait view
|
|
745
|
+
* and the user switches to landscape view
|
|
746
|
+
*/
|
|
747
|
+
const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
|
|
748
|
+
const { presentingEl } = opts;
|
|
749
|
+
if (!presentingEl) {
|
|
750
|
+
// No transition needed for non-card modals
|
|
751
|
+
return createAnimation('portrait-to-landscape-transition');
|
|
752
|
+
}
|
|
753
|
+
const presentingElIsCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
|
|
754
|
+
const presentingElRoot = getElementRoot(presentingEl);
|
|
755
|
+
const bodyEl = document.body;
|
|
756
|
+
const baseAnimation = createAnimation('portrait-to-landscape-transition')
|
|
757
|
+
.addElement(baseEl)
|
|
758
|
+
.easing('cubic-bezier(0.32,0.72,0,1)')
|
|
759
|
+
.duration(duration);
|
|
760
|
+
const presentingAnimation = createAnimation().beforeStyles({
|
|
761
|
+
transform: 'translateY(0)',
|
|
762
|
+
'transform-origin': 'top center',
|
|
763
|
+
overflow: 'hidden',
|
|
764
|
+
});
|
|
765
|
+
if (!presentingElIsCardModal) {
|
|
766
|
+
// The presenting element is not a card modal, so we do not
|
|
767
|
+
// need to care about layering and modal-specific styles.
|
|
768
|
+
const root = getElementRoot(baseEl);
|
|
769
|
+
const wrapperAnimation = createAnimation()
|
|
770
|
+
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
|
771
|
+
.fromTo('opacity', '1', '1'); // Keep wrapper visible in landscape
|
|
772
|
+
const backdropAnimation = createAnimation()
|
|
773
|
+
.addElement(root.querySelector('ion-backdrop'))
|
|
774
|
+
.fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
|
|
775
|
+
// Animate presentingEl from portrait state back to normal
|
|
776
|
+
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
|
777
|
+
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
778
|
+
const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
|
|
779
|
+
presentingAnimation
|
|
780
|
+
.addElement(presentingEl)
|
|
781
|
+
.afterStyles({
|
|
782
|
+
transform: 'translateY(0px) scale(1)',
|
|
783
|
+
'border-radius': '0px',
|
|
784
|
+
})
|
|
785
|
+
.beforeAddWrite(() => bodyEl.style.setProperty('background-color', ''))
|
|
786
|
+
.fromTo('transform', fromTransform, 'translateY(0px) scale(1)')
|
|
787
|
+
.fromTo('filter', 'contrast(0.85)', 'contrast(1)')
|
|
788
|
+
.fromTo('border-radius', '10px 10px 0 0', '0px');
|
|
789
|
+
baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
// The presenting element is a card modal, so we do
|
|
793
|
+
// need to care about layering and modal-specific styles.
|
|
794
|
+
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
795
|
+
const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
|
796
|
+
const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
|
797
|
+
presentingAnimation
|
|
798
|
+
.addElement(presentingElRoot.querySelector('.modal-wrapper'))
|
|
799
|
+
.afterStyles({
|
|
800
|
+
transform: toTransform,
|
|
801
|
+
})
|
|
802
|
+
.fromTo('transform', fromTransform, toTransform)
|
|
803
|
+
.fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
|
|
804
|
+
const shadowAnimation = createAnimation()
|
|
805
|
+
.addElement(presentingElRoot.querySelector('.modal-shadow'))
|
|
806
|
+
.afterStyles({
|
|
807
|
+
transform: toTransform,
|
|
808
|
+
})
|
|
809
|
+
.fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
|
|
810
|
+
.fromTo('transform', fromTransform, toTransform);
|
|
811
|
+
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
|
812
|
+
}
|
|
813
|
+
return baseAnimation;
|
|
814
|
+
};
|
|
815
|
+
/**
|
|
816
|
+
* Transition animation from landscape view to portrait view
|
|
817
|
+
* This handles the case where a card modal is open in landscape view
|
|
818
|
+
* and the user switches to portrait view
|
|
819
|
+
*/
|
|
820
|
+
const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
|
|
821
|
+
const { presentingEl } = opts;
|
|
822
|
+
if (!presentingEl) {
|
|
823
|
+
// No transition needed for non-card modals
|
|
824
|
+
return createAnimation('landscape-to-portrait-transition');
|
|
825
|
+
}
|
|
826
|
+
const presentingElIsCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
|
|
827
|
+
const presentingElRoot = getElementRoot(presentingEl);
|
|
828
|
+
const bodyEl = document.body;
|
|
829
|
+
const baseAnimation = createAnimation('landscape-to-portrait-transition')
|
|
830
|
+
.addElement(baseEl)
|
|
831
|
+
.easing('cubic-bezier(0.32,0.72,0,1)')
|
|
832
|
+
.duration(duration);
|
|
833
|
+
const presentingAnimation = createAnimation().beforeStyles({
|
|
834
|
+
transform: 'translateY(0)',
|
|
835
|
+
'transform-origin': 'top center',
|
|
836
|
+
overflow: 'hidden',
|
|
837
|
+
});
|
|
838
|
+
if (!presentingElIsCardModal) {
|
|
839
|
+
// The presenting element is not a card modal, so we do not
|
|
840
|
+
// need to care about layering and modal-specific styles.
|
|
841
|
+
const root = getElementRoot(baseEl);
|
|
842
|
+
const wrapperAnimation = createAnimation()
|
|
843
|
+
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
|
844
|
+
.fromTo('opacity', '1', '1'); // Keep wrapper visible
|
|
845
|
+
const backdropAnimation = createAnimation()
|
|
846
|
+
.addElement(root.querySelector('ion-backdrop'))
|
|
847
|
+
.fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
|
|
848
|
+
// Animate presentingEl from normal state to portrait state
|
|
849
|
+
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
|
850
|
+
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
851
|
+
const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
|
|
852
|
+
presentingAnimation
|
|
853
|
+
.addElement(presentingEl)
|
|
854
|
+
.beforeStyles({
|
|
855
|
+
transform: 'translateY(0px) scale(1)',
|
|
856
|
+
'transform-origin': 'top center',
|
|
857
|
+
overflow: 'hidden',
|
|
858
|
+
})
|
|
859
|
+
.afterStyles({
|
|
860
|
+
transform: toTransform,
|
|
861
|
+
'border-radius': '10px 10px 0 0',
|
|
862
|
+
filter: 'contrast(0.85)',
|
|
863
|
+
overflow: 'hidden',
|
|
864
|
+
'transform-origin': 'top center',
|
|
865
|
+
})
|
|
866
|
+
.beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
|
|
867
|
+
.keyframes([
|
|
868
|
+
{ offset: 0, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '0px' },
|
|
869
|
+
{ offset: 0.2, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '10px 10px 0 0' },
|
|
870
|
+
{ offset: 1, transform: toTransform, filter: 'contrast(0.85)', borderRadius: '10px 10px 0 0' },
|
|
871
|
+
]);
|
|
872
|
+
baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
// The presenting element is also a card modal, so we need
|
|
876
|
+
// to handle layering and modal-specific styles.
|
|
877
|
+
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
878
|
+
const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
|
879
|
+
const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
|
880
|
+
presentingAnimation
|
|
881
|
+
.addElement(presentingElRoot.querySelector('.modal-wrapper'))
|
|
882
|
+
.afterStyles({
|
|
883
|
+
transform: toTransform,
|
|
884
|
+
})
|
|
885
|
+
.fromTo('transform', fromTransform, toTransform)
|
|
886
|
+
.fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
|
|
887
|
+
const shadowAnimation = createAnimation()
|
|
888
|
+
.addElement(presentingElRoot.querySelector('.modal-shadow'))
|
|
889
|
+
.afterStyles({
|
|
890
|
+
transform: toTransform,
|
|
891
|
+
})
|
|
892
|
+
.fromTo('opacity', '0', '0') // Shadow stays hidden
|
|
893
|
+
.fromTo('transform', fromTransform, toTransform);
|
|
894
|
+
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
|
895
|
+
}
|
|
896
|
+
return baseAnimation;
|
|
897
|
+
};
|
|
898
|
+
|
|
742
899
|
const createEnterAnimation = () => {
|
|
743
900
|
const backdropAnimation = createAnimation()
|
|
744
901
|
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
|
@@ -1508,6 +1665,16 @@ const Modal = class {
|
|
|
1508
1665
|
triggerController.addClickListener(el, trigger);
|
|
1509
1666
|
}
|
|
1510
1667
|
}
|
|
1668
|
+
onWindowResize() {
|
|
1669
|
+
// Only handle resize for iOS card modals when no custom animations are provided
|
|
1670
|
+
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
clearTimeout(this.resizeTimeout);
|
|
1674
|
+
this.resizeTimeout = setTimeout(() => {
|
|
1675
|
+
this.handleViewTransition();
|
|
1676
|
+
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
1677
|
+
}
|
|
1511
1678
|
breakpointsChanged(breakpoints) {
|
|
1512
1679
|
if (breakpoints !== undefined) {
|
|
1513
1680
|
this.sortedBreakpoints = breakpoints.sort((a, b) => a - b);
|
|
@@ -1520,6 +1687,7 @@ const Modal = class {
|
|
|
1520
1687
|
}
|
|
1521
1688
|
disconnectedCallback() {
|
|
1522
1689
|
this.triggerController.removeClickListener();
|
|
1690
|
+
this.cleanupViewTransitionListener();
|
|
1523
1691
|
}
|
|
1524
1692
|
componentWillLoad() {
|
|
1525
1693
|
var _a;
|
|
@@ -1730,6 +1898,8 @@ const Modal = class {
|
|
|
1730
1898
|
else if (hasCardModal) {
|
|
1731
1899
|
this.initSwipeToClose();
|
|
1732
1900
|
}
|
|
1901
|
+
// Initialize view transition listener for iOS card modals
|
|
1902
|
+
this.initViewTransitionListener();
|
|
1733
1903
|
unlock();
|
|
1734
1904
|
}
|
|
1735
1905
|
initSwipeToClose() {
|
|
@@ -1883,6 +2053,7 @@ const Modal = class {
|
|
|
1883
2053
|
if (this.gesture) {
|
|
1884
2054
|
this.gesture.destroy();
|
|
1885
2055
|
}
|
|
2056
|
+
this.cleanupViewTransitionListener();
|
|
1886
2057
|
}
|
|
1887
2058
|
this.currentBreakpoint = undefined;
|
|
1888
2059
|
this.animation = undefined;
|
|
@@ -1958,6 +2129,108 @@ const Modal = class {
|
|
|
1958
2129
|
await this.setCurrentBreakpoint(nextBreakpoint);
|
|
1959
2130
|
return true;
|
|
1960
2131
|
}
|
|
2132
|
+
initViewTransitionListener() {
|
|
2133
|
+
// Only enable for iOS card modals when no custom animations are provided
|
|
2134
|
+
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
// Set initial view state
|
|
2138
|
+
this.currentViewIsPortrait = window.innerWidth < 768;
|
|
2139
|
+
}
|
|
2140
|
+
handleViewTransition() {
|
|
2141
|
+
const isPortrait = window.innerWidth < 768;
|
|
2142
|
+
// Only transition if view state actually changed
|
|
2143
|
+
if (this.currentViewIsPortrait === isPortrait) {
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
// Cancel any ongoing transition animation
|
|
2147
|
+
if (this.viewTransitionAnimation) {
|
|
2148
|
+
this.viewTransitionAnimation.destroy();
|
|
2149
|
+
this.viewTransitionAnimation = undefined;
|
|
2150
|
+
}
|
|
2151
|
+
const { presentingElement } = this;
|
|
2152
|
+
if (!presentingElement) {
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
// Create transition animation
|
|
2156
|
+
let transitionAnimation;
|
|
2157
|
+
if (this.currentViewIsPortrait && !isPortrait) {
|
|
2158
|
+
// Portrait to landscape transition
|
|
2159
|
+
transitionAnimation = portraitToLandscapeTransition(this.el, {
|
|
2160
|
+
presentingEl: presentingElement});
|
|
2161
|
+
}
|
|
2162
|
+
else {
|
|
2163
|
+
// Landscape to portrait transition
|
|
2164
|
+
transitionAnimation = landscapeToPortraitTransition(this.el, {
|
|
2165
|
+
presentingEl: presentingElement});
|
|
2166
|
+
}
|
|
2167
|
+
// Update state and play animation
|
|
2168
|
+
this.currentViewIsPortrait = isPortrait;
|
|
2169
|
+
this.viewTransitionAnimation = transitionAnimation;
|
|
2170
|
+
transitionAnimation.play().then(() => {
|
|
2171
|
+
this.viewTransitionAnimation = undefined;
|
|
2172
|
+
// After orientation transition, recreate the swipe-to-close gesture
|
|
2173
|
+
// with updated animation that reflects the new presenting element state
|
|
2174
|
+
this.reinitSwipeToClose();
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
cleanupViewTransitionListener() {
|
|
2178
|
+
// Clear any pending resize timeout
|
|
2179
|
+
if (this.resizeTimeout) {
|
|
2180
|
+
clearTimeout(this.resizeTimeout);
|
|
2181
|
+
this.resizeTimeout = undefined;
|
|
2182
|
+
}
|
|
2183
|
+
if (this.viewTransitionAnimation) {
|
|
2184
|
+
this.viewTransitionAnimation.destroy();
|
|
2185
|
+
this.viewTransitionAnimation = undefined;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
reinitSwipeToClose() {
|
|
2189
|
+
// Only reinitialize if we have a presenting element and are on iOS
|
|
2190
|
+
if (getIonMode(this) !== 'ios' || !this.presentingElement) {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
// Clean up existing gesture and animation
|
|
2194
|
+
if (this.gesture) {
|
|
2195
|
+
this.gesture.destroy();
|
|
2196
|
+
this.gesture = undefined;
|
|
2197
|
+
}
|
|
2198
|
+
if (this.animation) {
|
|
2199
|
+
// Properly end the progress-based animation at initial state before destroying
|
|
2200
|
+
// to avoid leaving modal in intermediate swipe position
|
|
2201
|
+
this.animation.progressEnd(0, 0, 0);
|
|
2202
|
+
this.animation.destroy();
|
|
2203
|
+
this.animation = undefined;
|
|
2204
|
+
}
|
|
2205
|
+
// Force the modal back to the correct position or it could end up
|
|
2206
|
+
// in a weird state after destroying the animation
|
|
2207
|
+
raf(() => {
|
|
2208
|
+
this.ensureCorrectModalPosition();
|
|
2209
|
+
this.initSwipeToClose();
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
ensureCorrectModalPosition() {
|
|
2213
|
+
const { el, presentingElement } = this;
|
|
2214
|
+
const root = getElementRoot(el);
|
|
2215
|
+
const wrapperEl = root.querySelector('.modal-wrapper');
|
|
2216
|
+
if (wrapperEl) {
|
|
2217
|
+
wrapperEl.style.transform = 'translateY(0vh)';
|
|
2218
|
+
wrapperEl.style.opacity = '1';
|
|
2219
|
+
}
|
|
2220
|
+
if (presentingElement) {
|
|
2221
|
+
const isPortrait = window.innerWidth < 768;
|
|
2222
|
+
if (isPortrait) {
|
|
2223
|
+
const transformOffset = !CSS.supports('width', 'max(0px, 1px)')
|
|
2224
|
+
? '30px'
|
|
2225
|
+
: 'max(30px, var(--ion-safe-area-top))';
|
|
2226
|
+
const scale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
2227
|
+
presentingElement.style.transform = `translateY(${transformOffset}) scale(${scale})`;
|
|
2228
|
+
}
|
|
2229
|
+
else {
|
|
2230
|
+
presentingElement.style.transform = 'translateY(0px) scale(1)';
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
1961
2234
|
render() {
|
|
1962
2235
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
1963
2236
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -1965,20 +2238,20 @@ const Modal = class {
|
|
|
1965
2238
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
1966
2239
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
1967
2240
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
1968
|
-
return (h(Host, Object.assign({ key: '
|
|
2241
|
+
return (h(Host, Object.assign({ key: '1980fa23331381c568a2be8091d888e09754fc52', "no-router": true,
|
|
1969
2242
|
// Allow the modal to be navigable when the handle is focusable
|
|
1970
2243
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
1971
2244
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
1972
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '
|
|
2245
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'ba94b055c064e2907eabbe6d7a43cb52adff1048', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '991f47859250d2143275ebb9b0b01a6ea8c491c0', class: "modal-shadow" }), h("div", Object.assign({ key: '02ecf8ac6a5bdb309ff993cc74a3911e99502a89',
|
|
1973
2246
|
/*
|
|
1974
2247
|
role and aria-modal must be used on the
|
|
1975
2248
|
same element. They must also be set inside the
|
|
1976
2249
|
shadow DOM otherwise ion-button will not be highlighted
|
|
1977
2250
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
1978
2251
|
*/
|
|
1979
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2252
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0180a4d6952e41bfd736272d1a49d47d86ca7fef', class: "modal-handle",
|
|
1980
2253
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
1981
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '
|
|
2254
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'd062f330675f730ad70c23267baed200ca9b43b0' }))));
|
|
1982
2255
|
}
|
|
1983
2256
|
get el() { return getElement(this); }
|
|
1984
2257
|
static get watchers() { return {
|