Skip to content

Commit

Permalink
fix(modal): status bar color now correct with sheet modal (#25424)
Browse files Browse the repository at this point in the history
resolves #20501
  • Loading branch information
liamdebeasi authored Jun 13, 2022
1 parent c10df52 commit 377c4f5
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 12 deletions.
32 changes: 31 additions & 1 deletion core/src/components/modal/gestures/swipe-to-close.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import type { GestureDetail } from '../../../utils/gesture';
import { createGesture } from '../../../utils/gesture';
import { clamp, getElementRoot } from '../../../utils/helpers';
import { setCardStatusBarDark, setCardStatusBarDefault } from '../utils';

import { calculateSpringStep, handleCanDismiss } from './utils';

Expand All @@ -18,13 +19,20 @@ export const SwipeToCloseDefaults = {
};

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;
let isOpen = false;
let canDismissBlocksGesture = false;
let contentEl: HTMLElement | null = null;
let scrollEl: HTMLElement | null = null;
const canDismissMaxStep = 0.2;
let initialScrollY = true;
let lastStep = 0;
const getScrollY = () => {
if (contentEl && isIonContent(contentEl)) {
return (contentEl as HTMLIonContentElement).scrollY;
Expand Down Expand Up @@ -187,6 +195,28 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
const clampedStep = clamp(0.0001, processedStep, maxStep);

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) => {
Expand All @@ -208,7 +238,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
* animation can never complete until
* canDismiss is checked.
*/
const shouldComplete = !isAttempingDismissWithCanDismiss && threshold >= 0.5;
const shouldComplete = !isAttempingDismissWithCanDismiss && threshold >= DISMISS_THRESHOLD;
let newStepValue = shouldComplete ? -0.001 : 0.001;

if (!shouldComplete) {
Expand Down
44 changes: 33 additions & 11 deletions core/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { mdLeaveAnimation } from './animations/md.leave';
import type { MoveSheetToBreakpointOptions } from './gestures/sheet';
import { createSheetGesture } from './gestures/sheet';
import { createSwipeToCloseGesture } from './gestures/swipe-to-close';
import { setCardStatusBarDark, setCardStatusBarDefault } from './utils';

/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
Expand Down Expand Up @@ -466,21 +467,31 @@ export class Modal implements ComponentInterface, OverlayInterface {
backdropBreakpoint: this.backdropBreakpoint,
});

/**
* 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
* modal can be dismissed. This check should change to check the presence of
* presentingElement instead.
*
* 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.
*/
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();

/**
* 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
* modal can be dismissed. This check should change to check the presence of
* presentingElement instead.
*
* 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.
*/
} else if (this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined)) {
} else if (hasCardModal) {
await this.initSwipeToClose();
}

Expand Down Expand Up @@ -631,6 +642,17 @@ export class Modal implements ComponentInterface, OverlayInterface {
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 */
if (typeof window !== 'undefined' && this.keyboardOpenCallback) {
window.removeEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
Expand Down
31 changes: 31 additions & 0 deletions core/src/components/modal/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { StatusBar, Style } from '../../utils/native/status-bar';
import { win } from '../../utils/window';

/**
* Use y = mx + b to
* figure out the backdrop value
Expand Down Expand Up @@ -57,3 +60,31 @@ export const getBackdropValueForSheet = (x: number, backdropBreakpoint: number)

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 changes: 34 additions & 0 deletions core/src/utils/native/status-bar.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions core/src/utils/window/index.ts
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 377c4f5

Please sign in to comment.