+ Angular Overlay Components: Modals, Popovers with Custom Injectors
+
+
+
+Ionic provides overlay components such as modals and popovers that display content on top of your application. In Angular, these overlays can be created using controllers like `ModalController` and `PopoverController`.
+
+## Creating Overlays
+
+Overlays can be created programmatically using their respective controllers:
+
+```typescript
+import { Component } from '@angular/core';
+import { ModalController } from '@ionic/angular/standalone';
+import { MyModalComponent } from './my-modal.component';
+
+@Component({
+ selector: 'app-home',
+ templateUrl: './home.component.html',
+})
+export class HomeComponent {
+ constructor(private modalController: ModalController) {}
+
+ async openModal() {
+ const modal = await this.modalController.create({
+ component: MyModalComponent,
+ componentProps: {
+ title: 'My Modal',
+ },
+ });
+ await modal.present();
+ }
+}
+```
+
+## Custom Injectors
+
+By default, overlay components use the root injector for dependency injection. This means that services or tokens provided at the route level or within a specific component tree are not accessible inside the overlay.
+
+The `injector` option allows you to pass a custom Angular `Injector` when creating a modal or popover. This enables overlay components to access services and tokens that are not available in the root injector.
+
+### Use Cases
+
+Custom injectors are useful when you need to:
+
+- Access route-scoped services from within an overlay
+- Use Angular CDK's `Dir` directive for bidirectional text support
+- Access any providers that are not registered at the root level
+
+### Usage
+
+To use a custom injector, pass it to the `create()` method:
+
+```typescript
+import { Component, Injector } from '@angular/core';
+import { ModalController } from '@ionic/angular/standalone';
+import { MyModalComponent } from './my-modal.component';
+import { MyRouteService } from './my-route.service';
+
+@Component({
+ selector: 'app-feature',
+ templateUrl: './feature.component.html',
+ providers: [MyRouteService], // Service provided at route level
+})
+export class FeatureComponent {
+ constructor(private modalController: ModalController, private injector: Injector) {}
+
+ async openModal() {
+ const modal = await this.modalController.create({
+ component: MyModalComponent,
+ injector: this.injector, // Pass the component's injector
+ });
+ await modal.present();
+ }
+}
+```
+
+The modal component can now inject `MyRouteService`:
+
+```typescript
+import { Component, inject } from '@angular/core';
+import { MyRouteService } from '../my-route.service';
+
+@Component({
+ selector: 'app-my-modal',
+ templateUrl: './my-modal.component.html',
+})
+export class MyModalComponent {
+ private myRouteService = inject(MyRouteService);
+}
+```
+
+### Creating a Custom Injector
+
+You can also create a custom injector with specific providers:
+
+```typescript
+import { Component, Injector } from '@angular/core';
+import { ModalController } from '@ionic/angular/standalone';
+import { MyModalComponent } from './my-modal.component';
+import { MyService } from './my.service';
+
+@Component({
+ selector: 'app-feature',
+ templateUrl: './feature.component.html',
+})
+export class FeatureComponent {
+ constructor(private modalController: ModalController, private injector: Injector) {}
+
+ async openModal() {
+ const myService = new MyService();
+ myService.configure({ someOption: true });
+
+ const customInjector = Injector.create({
+ providers: [{ provide: MyService, useValue: myService }],
+ parent: this.injector,
+ });
+
+ const modal = await this.modalController.create({
+ component: MyModalComponent,
+ injector: customInjector,
+ });
+ await modal.present();
+ }
+}
+```
+
+### Using with Angular CDK Directionality
+
+A common use case is providing the Angular CDK `Dir` directive to overlays for bidirectional text support:
+
+```typescript
+import { Component, Injector } from '@angular/core';
+import { Dir } from '@angular/cdk/bidi';
+import { ModalController } from '@ionic/angular/standalone';
+import { MyModalComponent } from './my-modal.component';
+
+@Component({
+ selector: 'app-feature',
+ templateUrl: './feature.component.html',
+})
+export class FeatureComponent {
+ constructor(private modalController: ModalController, private injector: Injector) {}
+
+ async openModal() {
+ const modal = await this.modalController.create({
+ component: MyModalComponent,
+ injector: this.injector, // Includes Dir from component tree
+ });
+ await modal.present();
+ }
+}
+```
+
+### Popover Controller
+
+The `PopoverController` supports the same `injector` option:
+
+```typescript
+import { Component, Injector } from '@angular/core';
+import { PopoverController } from '@ionic/angular/standalone';
+import { MyPopoverComponent } from './my-popover.component';
+
+@Component({
+ selector: 'app-feature',
+ templateUrl: './feature.component.html',
+})
+export class FeatureComponent {
+ constructor(private popoverController: PopoverController, private injector: Injector) {}
+
+ async openPopover(event: Event) {
+ const popover = await this.popoverController.create({
+ component: MyPopoverComponent,
+ event: event,
+ injector: this.injector,
+ });
+ await popover.present();
+ }
+}
+```
+
+## Angular Options Types
+
+Ionic Angular exports its own `ModalOptions` and `PopoverOptions` types that extend the core options with Angular-specific properties like `injector`:
+
+- `ModalOptions` - Extends core `ModalOptions` with the `injector` property
+- `PopoverOptions` - Extends core `PopoverOptions` with the `injector` property
+
+These types are exported from `@ionic/angular` and `@ionic/angular/standalone`:
+
+```typescript
+import type { ModalOptions, PopoverOptions } from '@ionic/angular/standalone';
+```
+
+## Docs for Overlays in Ionic
+
+For full docs and to see usage examples, visit the docs page for each of the overlays in Ionic:
+
+- [Action Sheet](https://ionicframework.com/docs/api/action-sheet)
+- [Alert](https://ionicframework.com/docs/api/alert)
+- [Loading](https://ionicframework.com/docs/api/loading)
+- [Modal](https://ionicframework.com/docs/api/modal)
+- [Picker](https://ionicframework.com/docs/api/picker)
+- [Popover](https://ionicframework.com/docs/api/popover)
+- [Toast](https://ionicframework.com/docs/api/toast)
diff --git a/docs/api/datetime.md b/docs/api/datetime.md
index 252d8c8d945..9ba9253dd43 100644
--- a/docs/api/datetime.md
+++ b/docs/api/datetime.md
@@ -43,6 +43,7 @@ import MultipleDateSelection from '@site/static/usage/v8/datetime/multiple/index
import GlobalTheming from '@site/static/usage/v8/datetime/styling/global-theming/index.md';
import CalendarHeaderStyling from '@site/static/usage/v8/datetime/styling/calendar-header/index.md';
import CalendarDaysStyling from '@site/static/usage/v8/datetime/styling/calendar-days/index.md';
+import DatetimeHeaderStyling from '@site/static/usage/v8/datetime/styling/datetime-header/index.md';
import WheelStyling from '@site/static/usage/v8/datetime/styling/wheel-styling/index.md';
@@ -353,6 +354,16 @@ The benefit of this approach is that every component, not just `ion-datetime`, c
+### Datetime Header
+
+The datetime header manages the content for the `title` slot and the selected date.
+
+:::note
+The selected date will not render if `preferWheel` is set to `true`.
+:::
+
+
+
### Calender Header
The calendar header manages the date navigation controls (month/year picker and prev/next buttons) and the days of the week when using a grid style layout.
diff --git a/docs/api/modal.md b/docs/api/modal.md
index 1249e2a813b..2b3868b8190 100644
--- a/docs/api/modal.md
+++ b/docs/api/modal.md
@@ -210,6 +210,26 @@ A few things to keep in mind when creating custom dialogs:
* `ion-content` is intended to be used in full-page modals, cards, and sheets. If your custom dialog has a dynamic or unknown size, `ion-content` should not be used.
* Creating custom dialogs provides a way of ejecting from the default modal experience. As a result, custom dialogs should not be used with card or sheet modals.
+## Event Handling
+
+### Using `ionDragStart` and `ionDragEnd`
+
+The `ionDragStart` event is emitted as soon as the user begins a dragging gesture on the modal. This event fires at the moment the user initiates contact with the handle or modal surface, before any actual displacement occurs. It is particularly useful for preparing the interface for a transition, such as hiding certain interactive elements (like headers or buttons) to ensure a smooth dragging experience.
+
+The `ionDragEnd` event is emitted when the user completes the dragging gesture by releasing the modal. Like the move event, it includes the final [`ModalDragEventDetail`](#modaldrageventdetail) object. This event is commonly used to finalize state changes once the modal has come to a rest.
+
+import DragStartEndEvents from '@site/static/usage/v8/modal/drag-start-end-events/index.md';
+
+
+
+### Using `ionDragMove`
+
+The `ionDragMove` event is emitted continuously while the user is actively dragging the modal. This event provides a [`ModalDragEventDetail`](#modaldrageventdetail) object containing real-time data, essential for creating highly responsive UI updates that react instantly to the user's touch. For example, the `progress` value can be used to dynamically darken a header's opacity as the modal is dragged upward.
+
+import DragMoveEvent from '@site/static/usage/v8/modal/drag-move-event/index.md';
+
+
+
## Interfaces
### ModalOptions
@@ -251,6 +271,59 @@ interface ModalCustomEvent extends CustomEvent {
}
```
+### ModalDragEventDetail
+
+When using the `ionDragMove` and `ionDragEnd` events, the event detail contains the following properties:
+
+```typescript
+interface ModalDragEventDetail {
+ /**
+ * The current Y position of the modal.
+ *
+ * This can be used to determine how far the modal has been dragged.
+ */
+ currentY: number;
+ /**
+ * The change in Y position since the gesture started.
+ *
+ * This can be used to determine the direction of the drag.
+ */
+ deltaY: number;
+ /**
+ * The velocity of the drag in the Y direction.
+ *
+ * This can be used to determine how fast the modal is being dragged.
+ */
+ velocityY: number;
+ /**
+ * A number between 0 and 1.
+ *
+ * In a sheet modal, progress represents the relative position between
+ * the lowest and highest defined breakpoints.
+ *
+ * In a card modal, it measures the relative position between the
+ * bottom of the screen and the top of the modal when it is fully
+ * open.
+ *
+ * This can be used to style content based on how far the modal has
+ * been dragged.
+ */
+ progress: number;
+ /**
+ * If the modal is a sheet modal, this will be the breakpoint that
+ * the modal will snap to if the user lets go of the modal at the
+ * current moment.
+ *
+ * If it's a card modal, this property will not be included in the
+ * event payload.
+ *
+ * This can be used to style content based on where the modal will
+ * snap to upon release.
+ */
+ snapBreakpoint?: number;
+}
+```
+
## Accessibility
### Keyboard Interactions
diff --git a/docs/api/range.md b/docs/api/range.md
index fdb042bb3e9..0a949acd1d0 100644
--- a/docs/api/range.md
+++ b/docs/api/range.md
@@ -124,6 +124,8 @@ import CSSProps from '@site/static/usage/v8/range/theming/css-properties/index.m
Range includes [CSS Shadow Parts](#css-shadow-parts) to allow complete customization of specific element nodes within the Range component. CSS Shadow Parts offer the most customization capabilities and are the recommended approach when requiring advance styling with the Range component.
+When `dualKnobs` is enabled, additional Shadow Parts are exposed to allow each knob to be styled independently. These are available in two forms: **static identity parts** (`A` and `B`) and **dynamic position parts** (`lower` and `upper`). The A and B parts always refer to the same physical knobs, even if the knobs cross. In contrast, the lower and upper parts reflect the current value position and automatically swap if the knobs cross. This allows styling by consistent identity or by relative value within the range.
+
import CSSParts from '@site/static/usage/v8/range/theming/css-shadow-parts/index.md';
diff --git a/docs/api/refresher.md b/docs/api/refresher.md
index 891478317cf..ca0ad55a025 100644
--- a/docs/api/refresher.md
+++ b/docs/api/refresher.md
@@ -73,10 +73,10 @@ Developers should apply the following CSS to the scrollable container. This CSS
.ion-content-scroll-host::before,
.ion-content-scroll-host::after {
position: absolute;
-
+
width: 1px;
height: 1px;
-
+
content: "";
}
@@ -102,6 +102,17 @@ import Advanced from '@site/static/usage/v8/refresher/advanced/index.md';
+## Event Handling
+
+### Using `ionPullStart` and `ionPullEnd`
+
+The `ionPullStart` event is emitted when the user begins a pull gesture. This event fires when the user starts to pull the refresher down.
+
+The `ionPullEnd` event is emitted when the refresher returns to an inactive state, with a reason property of `'complete'` or `'cancel'` indicating whether the refresh operation completed successfully or was canceled.
+
+import PullStartEndEvents from '@site/static/usage/v8/refresher/pull-start-end-events/index.md';
+
+
## Interfaces
@@ -113,6 +124,14 @@ interface RefresherEventDetail {
}
```
+### RefresherPullEndEventDetail
+
+```typescript
+interface RefresherPullEndEventDetail {
+ reason: 'complete' | 'cancel';
+}
+```
+
### RefresherCustomEvent
While not required, this interface can be used in place of the `CustomEvent` interface for stronger typing with Ionic events emitted from this component.
@@ -124,6 +143,17 @@ interface RefresherCustomEvent extends CustomEvent {
}
```
+### RefresherPullEndCustomEvent
+
+While not required, this interface can be used in place of the `CustomEvent` interface for stronger typing with the `ionPullEnd` event.
+
+```typescript
+interface RefresherPullEndCustomEvent extends CustomEvent {
+ detail: RefresherPullEndEventDetail;
+ target: HTMLIonRefresherElement;
+}
+```
+
## Properties
diff --git a/sidebars.js b/sidebars.js
index d8cacb17545..06689adc351 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -87,6 +87,7 @@ module.exports = {
'angular/build-options',
'angular/lifecycle',
'angular/navigation',
+ 'angular/overlays',
'angular/injection-tokens',
'angular/virtual-scroll',
'angular/slides',
diff --git a/static/usage/v8/datetime/styling/calendar-header/angular/example_component_css.md b/static/usage/v8/datetime/styling/calendar-header/angular/example_component_css.md
index f95379ddaa9..f3ebd715337 100644
--- a/static/usage/v8/datetime/styling/calendar-header/angular/example_component_css.md
+++ b/static/usage/v8/datetime/styling/calendar-header/angular/example_component_css.md
@@ -1,9 +1,30 @@
```css
/*
- * Custom Datetime Calendar Header Part
+ * Custom Datetime Calendar Header Parts
* -------------------------------------------
*/
+ion-datetime::part(calendar-header) {
+ background-color: orange;
+}
+
ion-datetime::part(month-year-button) {
background-color: lightblue;
}
+
+ion-datetime::part(navigation-button) {
+ background-color: firebrick;
+}
+
+ion-datetime::part(previous-button) {
+ color: white;
+}
+
+ion-datetime::part(next-button) {
+ color: black;
+}
+
+ion-datetime::part(calendar-days-of-week) {
+ background-color: #9ad162;
+ color: white;
+}
```
diff --git a/static/usage/v8/datetime/styling/calendar-header/demo.html b/static/usage/v8/datetime/styling/calendar-header/demo.html
index 331140d4bb4..5dfef5ca9bb 100644
--- a/static/usage/v8/datetime/styling/calendar-header/demo.html
+++ b/static/usage/v8/datetime/styling/calendar-header/demo.html
@@ -15,9 +15,30 @@
* -------------------------------------------
*/
+ ion-datetime::part(calendar-header) {
+ background-color: orange;
+ }
+
ion-datetime::part(month-year-button) {
background-color: lightblue;
}
+
+ ion-datetime::part(navigation-button) {
+ background-color: firebrick;
+ }
+
+ ion-datetime::part(previous-button) {
+ color: white;
+ }
+
+ ion-datetime::part(next-button) {
+ color: black;
+ }
+
+ ion-datetime::part(calendar-days-of-week) {
+ background-color: #9ad162;
+ color: white;
+ }
diff --git a/static/usage/v8/datetime/styling/calendar-header/javascript.md b/static/usage/v8/datetime/styling/calendar-header/javascript.md
index fc936e5efea..84ed00226c7 100644
--- a/static/usage/v8/datetime/styling/calendar-header/javascript.md
+++ b/static/usage/v8/datetime/styling/calendar-header/javascript.md
@@ -3,11 +3,32 @@
```
diff --git a/static/usage/v8/datetime/styling/calendar-header/react/main_css.md b/static/usage/v8/datetime/styling/calendar-header/react/main_css.md
index f95379ddaa9..f3ebd715337 100644
--- a/static/usage/v8/datetime/styling/calendar-header/react/main_css.md
+++ b/static/usage/v8/datetime/styling/calendar-header/react/main_css.md
@@ -1,9 +1,30 @@
```css
/*
- * Custom Datetime Calendar Header Part
+ * Custom Datetime Calendar Header Parts
* -------------------------------------------
*/
+ion-datetime::part(calendar-header) {
+ background-color: orange;
+}
+
ion-datetime::part(month-year-button) {
background-color: lightblue;
}
+
+ion-datetime::part(navigation-button) {
+ background-color: firebrick;
+}
+
+ion-datetime::part(previous-button) {
+ color: white;
+}
+
+ion-datetime::part(next-button) {
+ color: black;
+}
+
+ion-datetime::part(calendar-days-of-week) {
+ background-color: #9ad162;
+ color: white;
+}
```
diff --git a/static/usage/v8/datetime/styling/calendar-header/vue.md b/static/usage/v8/datetime/styling/calendar-header/vue.md
index 16b7da10a40..72d57d77391 100644
--- a/static/usage/v8/datetime/styling/calendar-header/vue.md
+++ b/static/usage/v8/datetime/styling/calendar-header/vue.md
@@ -12,8 +12,29 @@
* Custom Datetime Calendar Header Part
* -------------------------------------------
*/
+ ion-datetime::part(calendar-header) {
+ background-color: orange;
+ }
+
ion-datetime::part(month-year-button) {
background-color: lightblue;
}
+
+ ion-datetime::part(navigation-button) {
+ background-color: firebrick;
+ }
+
+ ion-datetime::part(previous-button) {
+ color: white;
+ }
+
+ ion-datetime::part(next-button) {
+ color: black;
+ }
+
+ ion-datetime::part(calendar-days-of-week) {
+ background-color: #9ad162;
+ color: white;
+ }
```
diff --git a/static/usage/v8/datetime/styling/datetime-header/angular/example_component_css.md b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_css.md
new file mode 100644
index 00000000000..d8e9e9e6341
--- /dev/null
+++ b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_css.md
@@ -0,0 +1,17 @@
+```css
+/*
+ * Custom Datetime Header Parts
+ * -------------------------------------------
+ */
+ion-datetime::part(datetime-header) {
+ background-color: orange;
+}
+
+ion-datetime::part(datetime-title) {
+ background-color: pink;
+}
+
+ion-datetime::part(datetime-selected-date) {
+ background-color: violet;
+}
+```
diff --git a/static/usage/v8/datetime/styling/datetime-header/angular/example_component_html.md b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_html.md
new file mode 100644
index 00000000000..64b144ee4e8
--- /dev/null
+++ b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_html.md
@@ -0,0 +1,5 @@
+```html
+
+ Select Date
+
+```
diff --git a/static/usage/v8/datetime/styling/datetime-header/angular/example_component_ts.md b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_ts.md
new file mode 100644
index 00000000000..75ed761f6e8
--- /dev/null
+++ b/static/usage/v8/datetime/styling/datetime-header/angular/example_component_ts.md
@@ -0,0 +1,12 @@
+```ts
+import { Component } from '@angular/core';
+import { IonDatetime } from '@ionic/angular/standalone';
+
+@Component({
+ selector: 'app-example',
+ templateUrl: 'example.component.html',
+ styleUrls: ['./example.component.css'],
+ imports: [IonDatetime],
+})
+export class ExampleComponent {}
+```
diff --git a/static/usage/v8/datetime/styling/datetime-header/demo.html b/static/usage/v8/datetime/styling/datetime-header/demo.html
new file mode 100644
index 00000000000..45155774edf
--- /dev/null
+++ b/static/usage/v8/datetime/styling/datetime-header/demo.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+ Datetime
+
+
+
+
+
+
+
+
+
+
+
+