@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/hydrate/index.js CHANGED
@@ -21218,7 +21218,7 @@ const iosEnterAnimation$3 = (baseEl, opts) => {
21218
21218
  baseAnimation.addAnimation(contentAnimation);
21219
21219
  }
21220
21220
  if (presentingEl) {
21221
- const isMobile = window.innerWidth < 768;
21221
+ const isPortrait = window.innerWidth < 768;
21222
21222
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
21223
21223
  const presentingElRoot = getElementRoot(presentingEl);
21224
21224
  const presentingAnimation = createAnimation().beforeStyles({
@@ -21227,7 +21227,7 @@ const iosEnterAnimation$3 = (baseEl, opts) => {
21227
21227
  overflow: 'hidden',
21228
21228
  });
21229
21229
  const bodyEl = document.body;
21230
- if (isMobile) {
21230
+ if (isPortrait) {
21231
21231
  /**
21232
21232
  * Fallback for browsers that does not support `max()` (ex: Firefox)
21233
21233
  * No need to worry about statusbar padding since engines like Gecko
@@ -21305,7 +21305,7 @@ const iosLeaveAnimation$3 = (baseEl, opts, duration = 500) => {
21305
21305
  .duration(duration)
21306
21306
  .addAnimation(wrapperAnimation);
21307
21307
  if (presentingEl) {
21308
- const isMobile = window.innerWidth < 768;
21308
+ const isPortrait = window.innerWidth < 768;
21309
21309
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
21310
21310
  const presentingElRoot = getElementRoot(presentingEl);
21311
21311
  const presentingAnimation = createAnimation()
@@ -21323,7 +21323,7 @@ const iosLeaveAnimation$3 = (baseEl, opts, duration = 500) => {
21323
21323
  }
21324
21324
  });
21325
21325
  const bodyEl = document.body;
21326
- if (isMobile) {
21326
+ if (isPortrait) {
21327
21327
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
21328
21328
  const modalTransform = hasCardModal ? '-10px' : transformOffset;
21329
21329
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
@@ -21370,6 +21370,163 @@ const iosLeaveAnimation$3 = (baseEl, opts, duration = 500) => {
21370
21370
  return baseAnimation;
21371
21371
  };
21372
21372
 
21373
+ /**
21374
+ * Transition animation from portrait view to landscape view
21375
+ * This handles the case where a card modal is open in portrait view
21376
+ * and the user switches to landscape view
21377
+ */
21378
+ const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
21379
+ const { presentingEl } = opts;
21380
+ if (!presentingEl) {
21381
+ // No transition needed for non-card modals
21382
+ return createAnimation('portrait-to-landscape-transition');
21383
+ }
21384
+ const presentingElIsCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
21385
+ const presentingElRoot = getElementRoot(presentingEl);
21386
+ const bodyEl = document.body;
21387
+ const baseAnimation = createAnimation('portrait-to-landscape-transition')
21388
+ .addElement(baseEl)
21389
+ .easing('cubic-bezier(0.32,0.72,0,1)')
21390
+ .duration(duration);
21391
+ const presentingAnimation = createAnimation().beforeStyles({
21392
+ transform: 'translateY(0)',
21393
+ 'transform-origin': 'top center',
21394
+ overflow: 'hidden',
21395
+ });
21396
+ if (!presentingElIsCardModal) {
21397
+ // The presenting element is not a card modal, so we do not
21398
+ // need to care about layering and modal-specific styles.
21399
+ const root = getElementRoot(baseEl);
21400
+ const wrapperAnimation = createAnimation()
21401
+ .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
21402
+ .fromTo('opacity', '1', '1'); // Keep wrapper visible in landscape
21403
+ const backdropAnimation = createAnimation()
21404
+ .addElement(root.querySelector('ion-backdrop'))
21405
+ .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
21406
+ // Animate presentingEl from portrait state back to normal
21407
+ const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
21408
+ const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
21409
+ const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
21410
+ presentingAnimation
21411
+ .addElement(presentingEl)
21412
+ .afterStyles({
21413
+ transform: 'translateY(0px) scale(1)',
21414
+ 'border-radius': '0px',
21415
+ })
21416
+ .beforeAddWrite(() => bodyEl.style.setProperty('background-color', ''))
21417
+ .fromTo('transform', fromTransform, 'translateY(0px) scale(1)')
21418
+ .fromTo('filter', 'contrast(0.85)', 'contrast(1)')
21419
+ .fromTo('border-radius', '10px 10px 0 0', '0px');
21420
+ baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
21421
+ }
21422
+ else {
21423
+ // The presenting element is a card modal, so we do
21424
+ // need to care about layering and modal-specific styles.
21425
+ const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
21426
+ const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
21427
+ const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
21428
+ presentingAnimation
21429
+ .addElement(presentingElRoot.querySelector('.modal-wrapper'))
21430
+ .afterStyles({
21431
+ transform: toTransform,
21432
+ })
21433
+ .fromTo('transform', fromTransform, toTransform)
21434
+ .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
21435
+ const shadowAnimation = createAnimation()
21436
+ .addElement(presentingElRoot.querySelector('.modal-shadow'))
21437
+ .afterStyles({
21438
+ transform: toTransform,
21439
+ })
21440
+ .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
21441
+ .fromTo('transform', fromTransform, toTransform);
21442
+ baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
21443
+ }
21444
+ return baseAnimation;
21445
+ };
21446
+ /**
21447
+ * Transition animation from landscape view to portrait view
21448
+ * This handles the case where a card modal is open in landscape view
21449
+ * and the user switches to portrait view
21450
+ */
21451
+ const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
21452
+ const { presentingEl } = opts;
21453
+ if (!presentingEl) {
21454
+ // No transition needed for non-card modals
21455
+ return createAnimation('landscape-to-portrait-transition');
21456
+ }
21457
+ const presentingElIsCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
21458
+ const presentingElRoot = getElementRoot(presentingEl);
21459
+ const bodyEl = document.body;
21460
+ const baseAnimation = createAnimation('landscape-to-portrait-transition')
21461
+ .addElement(baseEl)
21462
+ .easing('cubic-bezier(0.32,0.72,0,1)')
21463
+ .duration(duration);
21464
+ const presentingAnimation = createAnimation().beforeStyles({
21465
+ transform: 'translateY(0)',
21466
+ 'transform-origin': 'top center',
21467
+ overflow: 'hidden',
21468
+ });
21469
+ if (!presentingElIsCardModal) {
21470
+ // The presenting element is not a card modal, so we do not
21471
+ // need to care about layering and modal-specific styles.
21472
+ const root = getElementRoot(baseEl);
21473
+ const wrapperAnimation = createAnimation()
21474
+ .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
21475
+ .fromTo('opacity', '1', '1'); // Keep wrapper visible
21476
+ const backdropAnimation = createAnimation()
21477
+ .addElement(root.querySelector('ion-backdrop'))
21478
+ .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
21479
+ // Animate presentingEl from normal state to portrait state
21480
+ const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
21481
+ const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
21482
+ const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
21483
+ presentingAnimation
21484
+ .addElement(presentingEl)
21485
+ .beforeStyles({
21486
+ transform: 'translateY(0px) scale(1)',
21487
+ 'transform-origin': 'top center',
21488
+ overflow: 'hidden',
21489
+ })
21490
+ .afterStyles({
21491
+ transform: toTransform,
21492
+ 'border-radius': '10px 10px 0 0',
21493
+ filter: 'contrast(0.85)',
21494
+ overflow: 'hidden',
21495
+ 'transform-origin': 'top center',
21496
+ })
21497
+ .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
21498
+ .keyframes([
21499
+ { offset: 0, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '0px' },
21500
+ { offset: 0.2, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '10px 10px 0 0' },
21501
+ { offset: 1, transform: toTransform, filter: 'contrast(0.85)', borderRadius: '10px 10px 0 0' },
21502
+ ]);
21503
+ baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
21504
+ }
21505
+ else {
21506
+ // The presenting element is also a card modal, so we need
21507
+ // to handle layering and modal-specific styles.
21508
+ const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
21509
+ const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
21510
+ const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
21511
+ presentingAnimation
21512
+ .addElement(presentingElRoot.querySelector('.modal-wrapper'))
21513
+ .afterStyles({
21514
+ transform: toTransform,
21515
+ })
21516
+ .fromTo('transform', fromTransform, toTransform)
21517
+ .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
21518
+ const shadowAnimation = createAnimation()
21519
+ .addElement(presentingElRoot.querySelector('.modal-shadow'))
21520
+ .afterStyles({
21521
+ transform: toTransform,
21522
+ })
21523
+ .fromTo('opacity', '0', '0') // Shadow stays hidden
21524
+ .fromTo('transform', fromTransform, toTransform);
21525
+ baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
21526
+ }
21527
+ return baseAnimation;
21528
+ };
21529
+
21373
21530
  const createEnterAnimation = () => {
21374
21531
  const backdropAnimation = createAnimation()
21375
21532
  .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
@@ -22149,6 +22306,16 @@ class Modal {
22149
22306
  triggerController.addClickListener(el, trigger);
22150
22307
  }
22151
22308
  }
22309
+ onWindowResize() {
22310
+ // Only handle resize for iOS card modals when no custom animations are provided
22311
+ if (getIonMode$1(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
22312
+ return;
22313
+ }
22314
+ clearTimeout(this.resizeTimeout);
22315
+ this.resizeTimeout = setTimeout(() => {
22316
+ this.handleViewTransition();
22317
+ }, 50); // Debounce to avoid excessive calls during active resizing
22318
+ }
22152
22319
  breakpointsChanged(breakpoints) {
22153
22320
  if (breakpoints !== undefined) {
22154
22321
  this.sortedBreakpoints = breakpoints.sort((a, b) => a - b);
@@ -22161,6 +22328,7 @@ class Modal {
22161
22328
  }
22162
22329
  disconnectedCallback() {
22163
22330
  this.triggerController.removeClickListener();
22331
+ this.cleanupViewTransitionListener();
22164
22332
  }
22165
22333
  componentWillLoad() {
22166
22334
  var _a;
@@ -22371,6 +22539,8 @@ class Modal {
22371
22539
  else if (hasCardModal) {
22372
22540
  this.initSwipeToClose();
22373
22541
  }
22542
+ // Initialize view transition listener for iOS card modals
22543
+ this.initViewTransitionListener();
22374
22544
  unlock();
22375
22545
  }
22376
22546
  initSwipeToClose() {
@@ -22524,6 +22694,7 @@ class Modal {
22524
22694
  if (this.gesture) {
22525
22695
  this.gesture.destroy();
22526
22696
  }
22697
+ this.cleanupViewTransitionListener();
22527
22698
  }
22528
22699
  this.currentBreakpoint = undefined;
22529
22700
  this.animation = undefined;
@@ -22599,6 +22770,108 @@ class Modal {
22599
22770
  await this.setCurrentBreakpoint(nextBreakpoint);
22600
22771
  return true;
22601
22772
  }
22773
+ initViewTransitionListener() {
22774
+ // Only enable for iOS card modals when no custom animations are provided
22775
+ if (getIonMode$1(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
22776
+ return;
22777
+ }
22778
+ // Set initial view state
22779
+ this.currentViewIsPortrait = window.innerWidth < 768;
22780
+ }
22781
+ handleViewTransition() {
22782
+ const isPortrait = window.innerWidth < 768;
22783
+ // Only transition if view state actually changed
22784
+ if (this.currentViewIsPortrait === isPortrait) {
22785
+ return;
22786
+ }
22787
+ // Cancel any ongoing transition animation
22788
+ if (this.viewTransitionAnimation) {
22789
+ this.viewTransitionAnimation.destroy();
22790
+ this.viewTransitionAnimation = undefined;
22791
+ }
22792
+ const { presentingElement } = this;
22793
+ if (!presentingElement) {
22794
+ return;
22795
+ }
22796
+ // Create transition animation
22797
+ let transitionAnimation;
22798
+ if (this.currentViewIsPortrait && !isPortrait) {
22799
+ // Portrait to landscape transition
22800
+ transitionAnimation = portraitToLandscapeTransition(this.el, {
22801
+ presentingEl: presentingElement});
22802
+ }
22803
+ else {
22804
+ // Landscape to portrait transition
22805
+ transitionAnimation = landscapeToPortraitTransition(this.el, {
22806
+ presentingEl: presentingElement});
22807
+ }
22808
+ // Update state and play animation
22809
+ this.currentViewIsPortrait = isPortrait;
22810
+ this.viewTransitionAnimation = transitionAnimation;
22811
+ transitionAnimation.play().then(() => {
22812
+ this.viewTransitionAnimation = undefined;
22813
+ // After orientation transition, recreate the swipe-to-close gesture
22814
+ // with updated animation that reflects the new presenting element state
22815
+ this.reinitSwipeToClose();
22816
+ });
22817
+ }
22818
+ cleanupViewTransitionListener() {
22819
+ // Clear any pending resize timeout
22820
+ if (this.resizeTimeout) {
22821
+ clearTimeout(this.resizeTimeout);
22822
+ this.resizeTimeout = undefined;
22823
+ }
22824
+ if (this.viewTransitionAnimation) {
22825
+ this.viewTransitionAnimation.destroy();
22826
+ this.viewTransitionAnimation = undefined;
22827
+ }
22828
+ }
22829
+ reinitSwipeToClose() {
22830
+ // Only reinitialize if we have a presenting element and are on iOS
22831
+ if (getIonMode$1(this) !== 'ios' || !this.presentingElement) {
22832
+ return;
22833
+ }
22834
+ // Clean up existing gesture and animation
22835
+ if (this.gesture) {
22836
+ this.gesture.destroy();
22837
+ this.gesture = undefined;
22838
+ }
22839
+ if (this.animation) {
22840
+ // Properly end the progress-based animation at initial state before destroying
22841
+ // to avoid leaving modal in intermediate swipe position
22842
+ this.animation.progressEnd(0, 0, 0);
22843
+ this.animation.destroy();
22844
+ this.animation = undefined;
22845
+ }
22846
+ // Force the modal back to the correct position or it could end up
22847
+ // in a weird state after destroying the animation
22848
+ raf(() => {
22849
+ this.ensureCorrectModalPosition();
22850
+ this.initSwipeToClose();
22851
+ });
22852
+ }
22853
+ ensureCorrectModalPosition() {
22854
+ const { el, presentingElement } = this;
22855
+ const root = getElementRoot(el);
22856
+ const wrapperEl = root.querySelector('.modal-wrapper');
22857
+ if (wrapperEl) {
22858
+ wrapperEl.style.transform = 'translateY(0vh)';
22859
+ wrapperEl.style.opacity = '1';
22860
+ }
22861
+ if (presentingElement) {
22862
+ const isPortrait = window.innerWidth < 768;
22863
+ if (isPortrait) {
22864
+ const transformOffset = !CSS.supports('width', 'max(0px, 1px)')
22865
+ ? '30px'
22866
+ : 'max(30px, var(--ion-safe-area-top))';
22867
+ const scale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
22868
+ presentingElement.style.transform = `translateY(${transformOffset}) scale(${scale})`;
22869
+ }
22870
+ else {
22871
+ presentingElement.style.transform = 'translateY(0px) scale(1)';
22872
+ }
22873
+ }
22874
+ }
22602
22875
  render() {
22603
22876
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
22604
22877
  const showHandle = handle !== false && isSheetModal;
@@ -22606,20 +22879,20 @@ class Modal {
22606
22879
  const isCardModal = presentingElement !== undefined && mode === 'ios';
22607
22880
  const isHandleCycle = handleBehavior === 'cycle';
22608
22881
  const isSheetModalWithHandle = isSheetModal && showHandle;
22609
- return (hAsync(Host, Object.assign({ key: '8add05bb43a2cdb5e3cf180147d31eb85a018fe0', "no-router": true,
22882
+ return (hAsync(Host, Object.assign({ key: '1980fa23331381c568a2be8091d888e09754fc52', "no-router": true,
22610
22883
  // Allow the modal to be navigable when the handle is focusable
22611
22884
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
22612
22885
  zIndex: `${20000 + this.overlayIndex}`,
22613
- }, 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 }), hAsync("ion-backdrop", { key: '90a6605a9564a699d6f66cf71cf6b506796a2963', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: 'a97d071395333bf803c0a9347bda000cf7500d8d', class: "modal-shadow" }), hAsync("div", Object.assign({ key: 'e7b7985c7414a13e3ba8dcecf497b76e92edf53e',
22886
+ }, 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 }), hAsync("ion-backdrop", { key: 'ba94b055c064e2907eabbe6d7a43cb52adff1048', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: '991f47859250d2143275ebb9b0b01a6ea8c491c0', class: "modal-shadow" }), hAsync("div", Object.assign({ key: '02ecf8ac6a5bdb309ff993cc74a3911e99502a89',
22614
22887
  /*
22615
22888
  role and aria-modal must be used on the
22616
22889
  same element. They must also be set inside the
22617
22890
  shadow DOM otherwise ion-button will not be highlighted
22618
22891
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
22619
22892
  */
22620
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '8258b65570b11a8ee9c9df2537d6419cd2e34536', class: "modal-handle",
22893
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '0180a4d6952e41bfd736272d1a49d47d86ca7fef', class: "modal-handle",
22621
22894
  // Prevents the handle from receiving keyboard focus when it does not cycle
22622
- 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) })), hAsync("slot", { key: '394370d0ed03ee03152f8f8abae7ff7664ca5c13' }))));
22895
+ 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) })), hAsync("slot", { key: 'd062f330675f730ad70c23267baed200ca9b43b0' }))));
22623
22896
  }
22624
22897
  get el() { return getElement(this); }
22625
22898
  static get watchers() { return {
@@ -22667,7 +22940,7 @@ class Modal {
22667
22940
  "setCurrentBreakpoint": [64],
22668
22941
  "getCurrentBreakpoint": [64]
22669
22942
  },
22670
- "$listeners$": undefined,
22943
+ "$listeners$": [[9, "resize", "onWindowResize"]],
22671
22944
  "$lazyBundleId$": "-",
22672
22945
  "$attrsToReflect$": []
22673
22946
  }; }