Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ ion-content,prop,mode,"ios" | "md",undefined,false,false
ion-content,prop,scrollEvents,boolean,false,false,false
ion-content,prop,scrollX,boolean,false,false,false
ion-content,prop,scrollY,boolean,true,false,false
ion-content,prop,tabBarScrollEffect,boolean | undefined,undefined,false,false
ion-content,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-content,method,getScrollElement,getScrollElement() => Promise<HTMLElement>
ion-content,method,scrollByPoint,scrollByPoint(x: number, y: number, duration: number) => Promise<void>
Expand Down Expand Up @@ -2397,7 +2398,7 @@ ion-tab-bar,shadow
ion-tab-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-tab-bar,prop,expand,"compact" | "full",'full',false,false
ion-tab-bar,prop,mode,"ios" | "md",undefined,false,false
ion-tab-bar,prop,scrollEffect,"hide" | undefined,undefined,false,true
ion-tab-bar,prop,scrollEffect,"fade" | "hide" | undefined,undefined,false,true
ion-tab-bar,prop,selectedTab,string | undefined,undefined,false,false
ion-tab-bar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-tab-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
Expand Down
23 changes: 17 additions & 6 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,10 @@ export namespace Components {
* @default true
*/
"scrollY": boolean;
/**
* Whether the scroll effect of an `ion-tab-bar` applies to this content. By default, the scroll effect only applies to the content of a page that cannot go back, such as the root page of a tab, so that the tab bar stays in place on pages that were navigated to. Set to `true` to apply the scroll effect on a page that can go back, or to `false` to turn it off for a page. Only applies to a tab bar that has a `scrollEffect` and is a child of `ion-tabs`.
*/
"tabBarScrollEffect"?: boolean;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -1471,7 +1475,7 @@ export namespace Components {
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling back quickly. A footer that is out of view is shown again when an element in it receives focus. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: FooterScrollEffect;
/**
Expand Down Expand Up @@ -1558,7 +1562,7 @@ export namespace Components {
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling back quickly. A header that is out of view is shown again when an element in it receives focus. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: HeaderScrollEffect;
/**
Expand Down Expand Up @@ -3947,7 +3951,7 @@ export namespace Components {
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling back quickly. A tab bar that is out of view is shown again when an element in it receives focus. `"fade"` fades the tab bar background out when the content is scrolled to the end. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
*/
"scrollEffect"?: TabBarScrollEffect;
/**
Expand Down Expand Up @@ -4869,6 +4873,7 @@ declare global {
new (): HTMLIonColElement;
};
interface HTMLIonContentElementEventMap {
"ionTabBarScrollEffectChange": void;
"ionScrollStart": ScrollBaseDetail;
"ionScroll": ScrollDetail;
"ionScrollEnd": ScrollBaseDetail;
Expand Down Expand Up @@ -7021,6 +7026,7 @@ declare namespace LocalJSX {
* Emitted when the scroll has started. This event is disabled by default. Set `scrollEvents` to `true` to enable.
*/
"onIonScrollStart"?: (event: IonContentCustomEvent<ScrollBaseDetail>) => void;
"onIonTabBarScrollEffectChange"?: (event: IonContentCustomEvent<void>) => void;
/**
* Because of performance reasons, ionScroll events are disabled by default, in order to enable them and start listening from (ionScroll), set this property to `true`.
* @default false
Expand All @@ -7036,6 +7042,10 @@ declare namespace LocalJSX {
* @default true
*/
"scrollY"?: boolean;
/**
* Whether the scroll effect of an `ion-tab-bar` applies to this content. By default, the scroll effect only applies to the content of a page that cannot go back, such as the root page of a tab, so that the tab bar stays in place on pages that were navigated to. Set to `true` to apply the scroll effect on a page that can go back, or to `false` to turn it off for a page. Only applies to a tab bar that has a `scrollEffect` and is a child of `ion-tabs`.
*/
"tabBarScrollEffect"?: boolean;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -7398,7 +7408,7 @@ declare namespace LocalJSX {
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling back quickly. A footer that is out of view is shown again when an element in it receives focus. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: FooterScrollEffect;
/**
Expand Down Expand Up @@ -7481,7 +7491,7 @@ declare namespace LocalJSX {
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling back quickly. A header that is out of view is shown again when an element in it receives focus. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: HeaderScrollEffect;
/**
Expand Down Expand Up @@ -9966,7 +9976,7 @@ declare namespace LocalJSX {
"onIonTabBarChanged"?: (event: IonTabBarCustomEvent<TabBarChangedEventDetail>) => void;
"onIonTabBarLoaded"?: (event: IonTabBarCustomEvent<void>) => void;
/**
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling back quickly. A tab bar that is out of view is shown again when an element in it receives focus. `"fade"` fades the tab bar background out when the content is scrolled to the end. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
*/
"scrollEffect"?: TabBarScrollEffect;
/**
Expand Down Expand Up @@ -10700,6 +10710,7 @@ declare namespace LocalJSX {
"scrollX": boolean;
"scrollY": boolean;
"scrollEvents": boolean;
"tabBarScrollEffect": boolean;
}
interface IonDatetimeAttributes {
"color": Color;
Expand Down
64 changes: 18 additions & 46 deletions core/src/components/content/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,57 +260,29 @@
transform: translateZ(0);
}

// Content: Header/Footer/Tab Bar Scroll Effect Hide — Gap Compensation
// Content: Header/Footer/Tab Bar Scroll Effect Hide
// --------------------------------------------------
// When a bar hides via scrollEffect="hide", it stays in the layout flow
// but is translated out of view. The inner-scroll must shift and expand
// to fill the gap so no empty space is visible to the user.

// Header gap compensation — only when content is NOT fullscreen.
// When fullscreen=true, inner-scroll already extends behind the header
// (offset-top = header height), so translateY would over-shift the content.
// A header, footer, or tab bar that hides on scroll stays in the layout and
// moves out of view while the content scrolls. The scroll area extends under
// the bars and is padded by the same amount, so nothing is covered while the
// bars are visible, and the content fills the space of a bar that moved out of
// view without changing the layout while scrolling.
// The variables are 0 for bars that are not part of the layout.

// Not needed when fullscreen=true, since the scroll area already extends
// behind the header and footer.
:host(.content-header-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), height 300ms cubic-bezier(0, 0, 0.2, 1);
}

:host(.content-header-hide-scroll-partner:not(.content-fullscreen).content-header-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-header-hide-height, 0px));

transform: translateY(calc(-1 * var(--internal-header-hide-height, 0px)));

transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
}

// Footer gap compensation — only when content is NOT fullscreen
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: height 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

:host(.content-footer-hide-scroll-partner:not(.content-fullscreen).content-footer-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-footer-hide-height, 0px));
top: calc(var(--internal-header-hide-height, 0px) * -1);

transition: height 350ms cubic-bezier(0.4, 0, 1, 1);
padding-top: calc(var(--padding-top) + var(--internal-header-hide-height, 0px));
}

// Tab bar gap compensation — only when content is NOT fullscreen
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: height 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen).content-tab-bar-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-tab-bar-hide-height, 0px));
bottom: calc((var(--internal-footer-hide-height, 0px) + var(--internal-tab-bar-hide-height, 0px)) * -1);

transition: height 350ms cubic-bezier(0.4, 0, 1, 1);
}

@media (prefers-reduced-motion: reduce) {
:host(.content-header-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-header-hide-scroll-partner:not(.content-fullscreen).content-header-hide-scroll-hidden) .inner-scroll,
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen).content-footer-hide-scroll-hidden) .inner-scroll,
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen).content-tab-bar-hide-scroll-hidden)
.inner-scroll {
transition: none;
}
padding-bottom: calc(
var(--padding-bottom) + var(--keyboard-offset) + var(--internal-content-safe-area-padding-bottom, 0px) +
var(--internal-footer-hide-height, 0px) + var(--internal-tab-bar-hide-height, 0px)
);
}
22 changes: 22 additions & 0 deletions core/src/components/content/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,28 @@ export class Content implements ComponentInterface {
*/
@Prop() scrollEvents = false;

/**
* Whether the scroll effect of an `ion-tab-bar` applies to this content.
* By default, the scroll effect only applies to the content of a page that
* cannot go back, such as the root page of a tab, so that the tab bar stays
* in place on pages that were navigated to.
* Set to `true` to apply the scroll effect on a page that can go back, or to
* `false` to turn it off for a page.
* Only applies to a tab bar that has a `scrollEffect` and is a child of `ion-tabs`.
*/
@Prop() tabBarScrollEffect?: boolean;
@Watch('tabBarScrollEffect')
tabBarScrollEffectChanged() {
this.ionTabBarScrollEffectChange.emit();
}

/**
* @internal
* Emitted when `tabBarScrollEffect` changes, so that the tab bar
* can update which pages it applies its scroll effect to.
*/
@Event() ionTabBarScrollEffectChange!: EventEmitter<void>;

/**
* Emitted when the scroll has started. This event is disabled by default.
* Set `scrollEvents` to `true` to enable.
Expand Down
21 changes: 10 additions & 11 deletions core/src/components/footer/footer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,22 @@ ion-footer.footer-toolbar-padding ion-toolbar:last-of-type {
// Footer - Scroll Effect Hide
// --------------------------------------------------

// Slide back into view when scrolling up — spring ease-out snaps the footer
// back into place quickly and settles smoothly.
// The footer follows the scroll pixel by pixel. The offset is set
// by the scroll effect and is how far the footer is out of view.
ion-footer.footer-scroll-effect-hide {
transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
transform: translateY(var(--internal-scroll-hide-offset, 0px));

// Slide out of view when scrolling down — footer moves off the bottom of the
// screen. Slower duration mirrors the natural pace of the downward scroll.
ion-footer.footer-scroll-effect-hide.footer-scroll-hidden {
transform: translateY(100%);
// Fades out so that it does not stay in view when it comes to rest above another bar
opacity: calc(1 - var(--internal-scroll-hide-progress, 0));
}

transition: transform 350ms cubic-bezier(0.4, 0, 1, 1);
// Only animate when the footer settles after scrolling stops.
ion-footer.footer-scroll-effect-hide.footer-scroll-settling {
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
ion-footer.footer-scroll-effect-hide,
ion-footer.footer-scroll-effect-hide.footer-scroll-hidden {
ion-footer.footer-scroll-effect-hide.footer-scroll-settling {
transition: none;
}
}
5 changes: 4 additions & 1 deletion core/src/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export class Footer implements ComponentInterface {
/**
* Describes the scroll effect that will be applied to the footer.
* `"hide"` slides the footer out of view when scrolling down and back in
* when scrolling up.
* when scrolling back quickly. A footer that is out of view is shown again
* when an element in it receives focus.
* `"fade"` fades the toolbar background on scroll.
*/
@Prop({ reflect: true }) scrollEffect?: FooterScrollEffect;
Expand Down Expand Up @@ -171,8 +172,10 @@ export class Footer implements ComponentInterface {
private setupScrollEffectHide = async (contentEl: HTMLElement) => {
const setupPromise = createScrollHideController(contentEl, {
el: this.el,
edge: 'bottom',
cssVar: '--internal-footer-hide-height',
hiddenClass: 'footer-scroll-hidden',
settlingClass: 'footer-scroll-settling',
contentPartnerClass: 'content-footer-hide-scroll-partner',
contentHiddenClass: 'content-footer-hide-scroll-hidden',
});
Expand Down
Loading
Loading