mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
fix(modal): status bar color now correct with sheet modal (#25424)
resolves #20501
This commit is contained in:
@ -9,6 +9,7 @@ import {
|
|||||||
import type { GestureDetail } from '../../../utils/gesture';
|
import type { GestureDetail } from '../../../utils/gesture';
|
||||||
import { createGesture } from '../../../utils/gesture';
|
import { createGesture } from '../../../utils/gesture';
|
||||||
import { clamp, getElementRoot } from '../../../utils/helpers';
|
import { clamp, getElementRoot } from '../../../utils/helpers';
|
||||||
|
import { setCardStatusBarDark, setCardStatusBarDefault } from '../utils';
|
||||||
|
|
||||||
import { calculateSpringStep, handleCanDismiss } from './utils';
|
import { calculateSpringStep, handleCanDismiss } from './utils';
|
||||||
|
|
||||||
@ -18,6 +19,12 @@ export const SwipeToCloseDefaults = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: Animation, onDismiss: () => void) => {
|
export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: Animation, onDismiss: () => void) => {
|
||||||
|
/**
|
||||||
|
* The step value at which a card modal
|
||||||
|
* is eligible for dismissing via gesture.
|
||||||
|
*/
|
||||||
|
const DISMISS_THRESHOLD = 0.5;
|
||||||
|
|
||||||
const height = el.offsetHeight;
|
const height = el.offsetHeight;
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
let canDismissBlocksGesture = false;
|
let canDismissBlocksGesture = false;
|
||||||
@ -25,6 +32,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
|
|||||||
let scrollEl: HTMLElement | null = null;
|
let scrollEl: HTMLElement | null = null;
|
||||||
const canDismissMaxStep = 0.2;
|
const canDismissMaxStep = 0.2;
|
||||||
let initialScrollY = true;
|
let initialScrollY = true;
|
||||||
|
let lastStep = 0;
|
||||||
const getScrollY = () => {
|
const getScrollY = () => {
|
||||||
if (contentEl && isIonContent(contentEl)) {
|
if (contentEl && isIonContent(contentEl)) {
|
||||||
return (contentEl as HTMLIonContentElement).scrollY;
|
return (contentEl as HTMLIonContentElement).scrollY;
|
||||||
@ -187,6 +195,28 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
|
|||||||
const clampedStep = clamp(0.0001, processedStep, maxStep);
|
const clampedStep = clamp(0.0001, processedStep, maxStep);
|
||||||
|
|
||||||
animation.progressStep(clampedStep);
|
animation.progressStep(clampedStep);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When swiping down half way, the status bar style
|
||||||
|
* should be reset to its default value.
|
||||||
|
*
|
||||||
|
* We track lastStep so that we do not fire these
|
||||||
|
* functions on every onMove, only when the user has
|
||||||
|
* crossed a certain threshold.
|
||||||
|
*/
|
||||||
|
if (clampedStep >= DISMISS_THRESHOLD && lastStep < DISMISS_THRESHOLD) {
|
||||||
|
setCardStatusBarDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* However, if we swipe back up, then the
|
||||||
|
* status bar style should be set to have light
|
||||||
|
* text on a dark background.
|
||||||
|
*/
|
||||||
|
} else if (clampedStep < DISMISS_THRESHOLD && lastStep >= DISMISS_THRESHOLD) {
|
||||||
|
setCardStatusBarDark();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastStep = clampedStep;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEnd = (detail: GestureDetail) => {
|
const onEnd = (detail: GestureDetail) => {
|
||||||
@ -208,7 +238,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
|
|||||||
* animation can never complete until
|
* animation can never complete until
|
||||||
* canDismiss is checked.
|
* canDismiss is checked.
|
||||||
*/
|
*/
|
||||||
const shouldComplete = !isAttempingDismissWithCanDismiss && threshold >= 0.5;
|
const shouldComplete = !isAttempingDismissWithCanDismiss && threshold >= DISMISS_THRESHOLD;
|
||||||
let newStepValue = shouldComplete ? -0.001 : 0.001;
|
let newStepValue = shouldComplete ? -0.001 : 0.001;
|
||||||
|
|
||||||
if (!shouldComplete) {
|
if (!shouldComplete) {
|
||||||
|
@ -31,6 +31,7 @@ import { mdLeaveAnimation } from './animations/md.leave';
|
|||||||
import type { MoveSheetToBreakpointOptions } from './gestures/sheet';
|
import type { MoveSheetToBreakpointOptions } from './gestures/sheet';
|
||||||
import { createSheetGesture } from './gestures/sheet';
|
import { createSheetGesture } from './gestures/sheet';
|
||||||
import { createSwipeToCloseGesture } from './gestures/swipe-to-close';
|
import { createSwipeToCloseGesture } from './gestures/swipe-to-close';
|
||||||
|
import { setCardStatusBarDark, setCardStatusBarDefault } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||||
@ -466,11 +467,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
|||||||
backdropBreakpoint: this.backdropBreakpoint,
|
backdropBreakpoint: this.backdropBreakpoint,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.currentTransition;
|
|
||||||
|
|
||||||
if (this.isSheetModal) {
|
|
||||||
this.initSheetGesture();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO (FW-937) - In the next major release of Ionic, all card modals
|
* TODO (FW-937) - In the next major release of Ionic, all card modals
|
||||||
* will be swipeable by default. canDismiss will be used to determine if the
|
* will be swipeable by default. canDismiss will be used to determine if the
|
||||||
@ -480,7 +476,22 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
|||||||
* If we did not do this check, then not using swipeToClose would mean you could
|
* If we did not do this check, then not using swipeToClose would mean you could
|
||||||
* not run canDismiss on swipe as there would be no swipe gesture created.
|
* not run canDismiss on swipe as there would be no swipe gesture created.
|
||||||
*/
|
*/
|
||||||
} else if (this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined)) {
|
const hasCardModal = this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to change the status bar at the
|
||||||
|
* start of the animation so that it completes
|
||||||
|
* by the time the card animation is done.
|
||||||
|
*/
|
||||||
|
if (hasCardModal && getIonMode(this) === 'ios') {
|
||||||
|
setCardStatusBarDark();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.currentTransition;
|
||||||
|
|
||||||
|
if (this.isSheetModal) {
|
||||||
|
this.initSheetGesture();
|
||||||
|
} else if (hasCardModal) {
|
||||||
await this.initSwipeToClose();
|
await this.initSwipeToClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,6 +642,17 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to start the status bar change
|
||||||
|
* before the animation so that the change
|
||||||
|
* finishes when the dismiss animation does.
|
||||||
|
* TODO (FW-937)
|
||||||
|
*/
|
||||||
|
const hasCardModal = this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined);
|
||||||
|
if (hasCardModal && getIonMode(this) === 'ios') {
|
||||||
|
setCardStatusBarDefault();
|
||||||
|
}
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
if (typeof window !== 'undefined' && this.keyboardOpenCallback) {
|
if (typeof window !== 'undefined' && this.keyboardOpenCallback) {
|
||||||
window.removeEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
|
window.removeEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { StatusBar, Style } from '../../utils/native/status-bar';
|
||||||
|
import { win } from '../../utils/window';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use y = mx + b to
|
* Use y = mx + b to
|
||||||
* figure out the backdrop value
|
* figure out the backdrop value
|
||||||
@ -57,3 +60,31 @@ export const getBackdropValueForSheet = (x: number, backdropBreakpoint: number)
|
|||||||
|
|
||||||
return x * slope + b;
|
return x * slope + b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tablet/desktop card modal activates
|
||||||
|
* when the window width is >= 768.
|
||||||
|
* At that point, the presenting element
|
||||||
|
* is not transformed, so we do not need to
|
||||||
|
* adjust the status bar color.
|
||||||
|
*
|
||||||
|
* Note: We check supportsDefaultStatusBarStyle so that
|
||||||
|
* Capacitor <= 2 users do not get their status bar
|
||||||
|
* stuck in an inconsistent state due to a lack of
|
||||||
|
* support for Style.Default.
|
||||||
|
*/
|
||||||
|
export const setCardStatusBarDark = () => {
|
||||||
|
if (!win || win.innerWidth >= 768 || !StatusBar.supportsDefaultStatusBarStyle()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBar.setStyle({ style: Style.Dark });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setCardStatusBarDefault = () => {
|
||||||
|
if (!win || win.innerWidth >= 768 || !StatusBar.supportsDefaultStatusBarStyle()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBar.setStyle({ style: Style.Default });
|
||||||
|
};
|
||||||
|
34
core/src/utils/native/status-bar.ts
Normal file
34
core/src/utils/native/status-bar.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { win } from '../window';
|
||||||
|
|
||||||
|
interface StyleOptions {
|
||||||
|
style: Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Style {
|
||||||
|
Dark = 'DARK',
|
||||||
|
Light = 'LIGHT',
|
||||||
|
Default = 'DEFAULT',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusBar = {
|
||||||
|
getEngine() {
|
||||||
|
return (win as any)?.Capacitor?.isPluginAvailable('StatusBar') && (win as any)?.Capacitor.Plugins.StatusBar;
|
||||||
|
},
|
||||||
|
supportsDefaultStatusBarStyle() {
|
||||||
|
/**
|
||||||
|
* The 'DEFAULT' status bar style was added
|
||||||
|
* to the @capacitor/status-bar plugin in Capacitor 3.
|
||||||
|
* PluginHeaders is only supported in Capacitor 3+,
|
||||||
|
* so we can use this to detect Capacitor 3.
|
||||||
|
*/
|
||||||
|
return !!(win as any)?.Capacitor?.PluginHeaders;
|
||||||
|
},
|
||||||
|
setStyle(options: StyleOptions) {
|
||||||
|
const engine = this.getEngine();
|
||||||
|
if (!engine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.setStyle(options);
|
||||||
|
},
|
||||||
|
};
|
23
core/src/utils/window/index.ts
Normal file
23
core/src/utils/window/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* When accessing the window, it is important
|
||||||
|
* to account for SSR applications where the
|
||||||
|
* window is not available. Code that accesses
|
||||||
|
* window when it is not available will crash.
|
||||||
|
* Even checking if `window === undefined` will cause
|
||||||
|
* apps to crash in SSR.
|
||||||
|
*
|
||||||
|
* Use win below to access an SSR-safe version
|
||||||
|
* of the window.
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
* Before:
|
||||||
|
* if (window.innerWidth > 768) { ... }
|
||||||
|
*
|
||||||
|
* After:
|
||||||
|
* import { win } from 'path/to/this/file';
|
||||||
|
* if (win?.innerWidth > 768) { ... }
|
||||||
|
*
|
||||||
|
* Note: Code inside of this if-block will
|
||||||
|
* not run in an SSR environment.
|
||||||
|
*/
|
||||||
|
export const win: Window | undefined = typeof window !== 'undefined' ? window : undefined;
|
Reference in New Issue
Block a user