Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ea68986
feat(modal): added snapBreakpoints to sheet modals
kumibrr Dec 22, 2024
4970471
fix: removed trailing semicolon
kumibrr Dec 22, 2024
95c5314
chore: ran build
kumibrr Dec 26, 2024
1dcf5c9
refactor(modal): replaced snapBreakpoints array implementation with s…
kumibrr Jan 14, 2025
96c5e41
feat(modal): add contentAnimation support and animateContentHeight op…
kumibrr Jan 14, 2025
4d0d56f
Merge branch 'ionic-team:main' into main
kumibrr Jan 14, 2025
37c5832
feat(modal): added footer animation.
kumibrr Jan 27, 2025
566d3db
feat(modal): improved implementation
kumibrr Jan 27, 2025
9ab3733
fix: added back breakpoint: 0
kumibrr Jan 27, 2025
556cb6a
removed console.log
kumibrr Jan 28, 2025
ad90cd9
feat: added footer slide-in and slide-out animation
kumibrr Jan 28, 2025
396bf73
fix: footerAnimation did not exist at gestures initialization.
kumibrr Jan 28, 2025
ac11277
restored footer onMove and onEnd animations
kumibrr Jan 28, 2025
44b8152
refactor(modal): use a clone footer to prevent flickering
thetaPC Jan 29, 2025
804d043
fix(modal): add animation when scrollAtEdge is false
thetaPC Jan 29, 2025
59dd5b0
Merge pull request #1 from thetaPC/scroll
kumibrr Jan 29, 2025
4052a86
feat(modal): implement footer visibility swap for scrollAtEdge handling
kumibrr Jan 29, 2025
f4cf4c1
chore: added comments explaining footer visibility swaps
kumibrr Jan 29, 2025
df96686
feat(modal): added footer visibility swap based on scrollAtEdge in le…
kumibrr Jan 29, 2025
991e02a
fix(modal): padding value was always zero
kumibrr Jan 29, 2025
7c24b1f
chore(modal): minor fixes
kumibrr Jan 29, 2025
6d3473f
Merge remote-tracking branch 'upstream/main'
thetaPC Jan 29, 2025
6778e42
feat(modal): add padding to the first toolbar in modal sheets
kumibrr Jan 29, 2025
cef6fd7
fix(modal): limited padding-top to only apply on ios styles
kumibrr Jan 29, 2025
c8392bb
chore(modal): added explanation for padding-top
kumibrr Jan 29, 2025
dc4caaf
chore(modal): added section for sheet modal
kumibrr Jan 29, 2025
f6702a7
chore(vue): remove file
thetaPC Jan 29, 2025
b1726ec
Merge branch 'main' of github.com:kumibrr/ionic-framework
thetaPC Jan 29, 2025
42aa703
fix(modal): add missing parameter
thetaPC Jan 30, 2025
30e2d8b
test(modal): update snapshots
thetaPC Jan 30, 2025
3a79743
refactor(modal): add comments
thetaPC Jan 30, 2025
aca5855
Merge branch 'feature-8.5' into main
brandyscarney Jan 30, 2025
6e07a6f
refactor(modal): add requested changes
thetaPC Jan 31, 2025
80a826e
Merge branch 'main' of github.com:kumibrr/ionic-framework
thetaPC Jan 31, 2025
df2c331
chore(vue): run build
thetaPC Jan 31, 2025
e15ae91
fix(vue): add missing new line
thetaPC Jan 31, 2025
4ffa5f6
fix(modal): add footer check
thetaPC Jan 31, 2025
bbbdb87
chore(core): run build
thetaPC Jan 31, 2025
433f563
Update core/src/components/modal/animations/md.leave.ts
thetaPC Feb 1, 2025
a9ea2f4
Update core/src/components/modal/animations/ios.leave.ts
thetaPC Feb 1, 2025
c099027
Update core/src/components/modal/gestures/sheet.ts
thetaPC Feb 1, 2025
ce909b0
Update core/src/components/modal/gestures/sheet.ts
thetaPC Feb 1, 2025
c0d75b5
Update core/src/components/modal/gestures/sheet.ts
thetaPC Feb 1, 2025
6416ebe
Update core/src/components/modal/gestures/sheet.ts
thetaPC Feb 1, 2025
f6cc539
Update core/src/components/modal/gestures/sheet.ts
thetaPC Feb 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefin
ion-modal,prop,mode,"ios" | "md",undefined,false,false
ion-modal,prop,presentingElement,HTMLElement | undefined,undefined,false,false
ion-modal,prop,showBackdrop,boolean,true,false,false
ion-modal,prop,snapBreakpoints,number[] | undefined,undefined,false,false
ion-modal,prop,trigger,string | undefined,undefined,false,false
ion-modal,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
ion-modal,method,getCurrentBreakpoint,getCurrentBreakpoint() => Promise<number | undefined>
Expand Down
8 changes: 8 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,10 @@ export namespace Components {
* If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM.
*/
"showBackdrop": boolean;
/**
* The snapBreakpoints to use when creating a sheet modal. Each value in the array must be a decimal between 0 and 1 where 0 indicates the modal is fully closed and 1 indicates the modal is fully open. Values are relative to the height of the modal, not the height of the screen. One of the values in this array must be the value of the `initialBreakpoint` property and they must be a value in `breakpoints` property. The difference between `breakpoints` and `snapBreakpoints` is that `snapBreakpoints` allows the content to scroll, and the modal will only be draggable by the handle.
*/
"snapBreakpoints"?: number[];
/**
* An ID corresponding to the trigger element that causes the modal to open when clicked.
*/
Expand Down Expand Up @@ -6622,6 +6626,10 @@ declare namespace LocalJSX {
* If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM.
*/
"showBackdrop"?: boolean;
/**
* The snapBreakpoints to use when creating a sheet modal. Each value in the array must be a decimal between 0 and 1 where 0 indicates the modal is fully closed and 1 indicates the modal is fully open. Values are relative to the height of the modal, not the height of the screen. One of the values in this array must be the value of the `initialBreakpoint` property and they must be a value in `breakpoints` property. The difference between `breakpoints` and `snapBreakpoints` is that `snapBreakpoints` allows the content to scroll, and the modal will only be draggable by the handle.
*/
"snapBreakpoints"?: number[];
/**
* An ID corresponding to the trigger element that causes the modal to open when clicked.
*/
Expand Down
42 changes: 38 additions & 4 deletions core/src/components/modal/gestures/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createAnimation } from '@utils/animation/animation';
import { isIonContent, findClosestIonContent } from '@utils/content';
import { createGesture } from '@utils/gesture';
import { clamp, raf, getElementRoot } from '@utils/helpers';
Expand Down Expand Up @@ -49,6 +50,7 @@ export const createSheetGesture = (
backdropBreakpoint: number,
animation: Animation,
breakpoints: number[] = [],
snapBreakpoints: number[] = [],
getCurrentBreakpoint: () => number,
onDismiss: () => void,
onBreakpointChange: (breakpoint: number) => void
Expand All @@ -71,6 +73,10 @@ export const createSheetGesture = (
{ offset: 1, transform: 'translateY(100%)' },
],
BACKDROP_KEYFRAMES: backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop,
CONTENT_KEYFRAMES: [
{ offset: 0, maxHeight: '100%' },
{ offset: 1, maxHeight: '0%' },
],
};

const contentEl = baseEl.querySelector('ion-content');
Expand All @@ -79,10 +85,20 @@ export const createSheetGesture = (
let offset = 0;
let canDismissBlocksGesture = false;
const canDismissMaxStep = 0.95;
const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
const maxBreakpoint = breakpoints[breakpoints.length - 1];
const minBreakpoint = breakpoints[0];
const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
let contentAnimation: Animation | undefined;
if (snapBreakpoints.length > 0) {
contentAnimation = animation
.addAnimation(
createAnimation('contentAnimation')
.addElement(contentEl!.parentElement!)
.keyframes(SheetDefaults.CONTENT_KEYFRAMES)
)
.childAnimations.find((ani) => ani.id === 'contentAnimation');
}

const enableBackdrop = () => {
baseEl.style.setProperty('pointer-events', 'auto');
Expand Down Expand Up @@ -138,7 +154,7 @@ export const createSheetGesture = (
}
}

if (contentEl && currentBreakpoint !== maxBreakpoint) {
if (contentEl && currentBreakpoint !== maxBreakpoint && !snapBreakpoints.includes(currentBreakpoint)) {
contentEl.scrollY = false;
}

Expand All @@ -154,6 +170,13 @@ export const createSheetGesture = (
const contentEl = findClosestIonContent(detail.event.target! as HTMLElement);
currentBreakpoint = getCurrentBreakpoint();

/**
* If we are in a snap breakpoint, we should not allow the swipe to start.
*/
if (snapBreakpoints.includes(currentBreakpoint) && contentEl) {
return false;
}

if (currentBreakpoint === 1 && contentEl) {
/**
* The modal should never swipe to close on the content with a refresher.
Expand Down Expand Up @@ -323,6 +346,13 @@ export const createSheetGesture = (
},
]);

if (contentAnimation) {
contentAnimation.keyframes([
{ offset: 0, maxHeight: `${(1 - breakpointOffset) * 100}%` },
{ offset: 1, maxHeight: `${snapToBreakpoint * 100}%` },
]);
}

animation.progressStep(0);
}

Expand All @@ -345,7 +375,10 @@ export const createSheetGesture = (
* re-enabled. Native iOS allows for scrolling on the sheet modal as soon
* as the gesture is released, so we align with that.
*/
if (contentEl && snapToBreakpoint === breakpoints[breakpoints.length - 1]) {
if (
contentEl &&
(snapToBreakpoint === breakpoints[breakpoints.length - 1] || snapBreakpoints.includes(snapToBreakpoint))
) {
contentEl.scrollY = true;
}

Expand All @@ -365,6 +398,7 @@ export const createSheetGesture = (
raf(() => {
wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
contentAnimation?.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
animation.progressStart(true, 1 - snapToBreakpoint);
currentBreakpoint = snapToBreakpoint;
onBreakpointChange(currentBreakpoint);
Expand Down
22 changes: 22 additions & 0 deletions core/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
private wrapperEl?: HTMLElement;
private backdropEl?: HTMLIonBackdropElement;
private sortedBreakpoints?: number[];
private sortedSnapBreakpoints?: number[];
private keyboardOpenCallback?: () => void;
private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => Promise<void>;
private inheritedAttributes: Attributes = {};
Expand Down Expand Up @@ -130,6 +131,19 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/
@Prop() breakpoints?: number[];

/**
* The snapBreakpoints to use when creating a sheet modal. Each value in the
* array must be a decimal between 0 and 1 where 0 indicates the modal is fully
* closed and 1 indicates the modal is fully open. Values are relative
* to the height of the modal, not the height of the screen. One of the values in this
* array must be the value of the `initialBreakpoint` property and they must be a
* value in `breakpoints` property.
*
* The difference between `breakpoints` and `snapBreakpoints` is that `snapBreakpoints`
* allows the content to scroll, and the modal will only be draggable by the handle.
*/
@Prop() snapBreakpoints?: number[];

/**
* A decimal value between 0 and 1 that indicates the
* initial point the modal will open at when creating a
Expand Down Expand Up @@ -354,6 +368,12 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
}

snapBreakpointsChanged(snapBreakpoints: number[] | undefined) {
if (snapBreakpoints !== undefined) {
this.sortedSnapBreakpoints = snapBreakpoints.sort((a, b) => a - b);
}
}

connectedCallback() {
const { el } = this;
prepareOverlay(el);
Expand Down Expand Up @@ -429,6 +449,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
raf(() => this.present());
}
this.breakpointsChanged(this.breakpoints);
this.snapBreakpointsChanged(this.snapBreakpoints);

/**
* When binding values in frameworks such as Angular
Expand Down Expand Up @@ -680,6 +701,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
backdropBreakpoint,
ani,
this.sortedBreakpoints,
this.sortedSnapBreakpoints,
() => this.currentBreakpoint ?? 0,
() => this.sheetOnDismiss(),
(breakpoint: number) => {
Expand Down
6 changes: 6 additions & 0 deletions core/src/components/modal/test/sheet/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@
>
Present Sheet Modal (Max breakpoint is not 1)
</button>
<button
id="custom-breakpoint-modal"
onclick="presentModal({ initialBreakpoint: 0.5, breakpoints: [0,0.25, 0.5, 0.75], snapBreakpoints: [0.5, 0.75] })"
>
Present Sheet Modal (SnapBreakpoints)
</button>
<button
id="custom-backdrop-modal"
onclick="presentModal({ backdropBreakpoint: 0.5, initialBreakpoint: 0.5 })"
Expand Down
Loading