src/modal/modal.component.ts
Component to create modals for presenting content.
Using a modal in your application requires cds-placeholder
which would generally be
placed near the end of your app component template (app.component.ts or app.component.html) as:
<cds-placeholder></cds-placeholder>
A more complete example for Modal
is given as follows:
Example modal definition:
Example :@Component({
selector: "app-sample-modal",
template: `
<cds-modal size="xl" (overlaySelected)="closeModal()">
<cds-modal-header (closeSelect)="closeModal()">Header text</cds-modal-header>
<section class="modal-body">
<h1>Sample modal works.</h1>
<button class="btn--icon-link" nPopover="Hello there" title="Popover title" placement="right" appendInline="true">
<svg cdsIcon="info" size="sm"></svg>
</button>
{{modalText}}
</section>
<cds-modal-footer><button cdsButton="primary" (click)="closeModal()">Close</button></cds-modal-footer>
</cds-modal>`,
styleUrls: ["./sample-modal.component.scss"]
})
export class SampleModal extends BaseModal {
modalText: string;
constructor(protected injector: Injector) {
super();
this.modalText = this.injector.get("modalText");
}
}
Example of opening the modal:
Example :@Component({
selector: "app-modal-demo",
template: `
<button cdsButton="primary" (click)="openModal('drill')">Drill-down modal</button>
<cds-placeholder></cds-placeholder>`
})
export class ModalDemo {
openModal() {
this.modalService.create({component: SampleModal, inputs: {modalText: "Hello universe."}});
}
}
AfterViewInit
OnChanges
OnDestroy
selector | cds-modal, ibm-modal |
template |
|
Properties |
Methods |
|
Inputs |
Outputs |
HostListeners |
Accessors |
constructor(modalService: BaseModalService, document: Document, renderer: Renderer2)
|
||||||||||||
Defined in src/modal/modal.component.ts:152
|
||||||||||||
Creates an instance of
Parameters :
|
ariaLabel | |
Type : string
|
|
Default value : "default"
|
|
Defined in src/modal/modal.component.ts:116
|
|
Label for the modal. |
hasScrollingContent | |
Type : boolean
|
|
Default value : null
|
|
Defined in src/modal/modal.component.ts:134
|
|
Specify whether the modal contains scrolling content. This property overrides the automatic
detection of the existence of scrolling content. Set this property to |
open | |
Type : boolean
|
|
Default value : false
|
|
Defined in src/modal/modal.component.ts:121
|
|
Controls the visibility of the modal when used directly in a template |
size | |
Type : "xs" | "sm" | "md" | "lg"
|
|
Default value : "md"
|
|
Defined in src/modal/modal.component.ts:107
|
|
Size of the modal to display. |
theme | |
Type : "default" | "danger"
|
|
Default value : "default"
|
|
Defined in src/modal/modal.component.ts:111
|
|
Classification of the modal. |
trigger | |
Type : HTMLElement
|
|
Defined in src/modal/modal.component.ts:126
|
|
The element that triggers the modal, which should receive focus when the modal closes |
close | |
Type : EventEmitter
|
|
Defined in src/modal/modal.component.ts:143
|
|
To emit the closing event of the modal window. |
overlaySelected | |
Type : EventEmitter
|
|
Defined in src/modal/modal.component.ts:139
|
|
Emits event when click occurs within |
keydown |
Arguments : '$event'
|
keydown(event: KeyboardEvent)
|
Defined in src/modal/modal.component.ts:195
|
Handle keyboard events to close modal and tab through the content within the modal. |
Protected focusInitialElement |
focusInitialElement()
|
Defined in src/modal/modal.component.ts:237
|
Returns :
void
|
handleKeyboardEvent | ||||||
handleKeyboardEvent(event: KeyboardEvent)
|
||||||
Decorators :
@HostListener('keydown', ['$event'])
|
||||||
Defined in src/modal/modal.component.ts:195
|
||||||
Handle keyboard events to close modal and tab through the content within the modal.
Parameters :
Returns :
void
|
ngAfterViewInit |
ngAfterViewInit()
|
Defined in src/modal/modal.component.ts:186
|
Set document focus to be on the modal component after it is initialized.
Returns :
void
|
ngOnChanges | |||||
ngOnChanges(undefined: SimpleChanges)
|
|||||
Defined in src/modal/modal.component.ts:163
|
|||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Defined in src/modal/modal.component.ts:233
|
Returns :
void
|
Private updateScrollbar |
updateScrollbar()
|
Defined in src/modal/modal.component.ts:248
|
Returns :
void
|
modal |
Type : ElementRef
|
Decorators :
@ViewChild('modal', {static: true})
|
Defined in src/modal/modal.component.ts:147
|
Maintains a reference to the view DOM element of the |
Public modalService |
Type : BaseModalService
|
Defined in src/modal/modal.component.ts:158
|
selectorPrimaryFocus |
Type : string
|
Default value : "[modal-primary-focus]"
|
Defined in src/modal/modal.component.ts:152
|
An element should have 'modal-primary-focus' as an attribute to receive initial focus within the |
shouldShowScrollbar |
getshouldShowScrollbar()
|
Defined in src/modal/modal.component.ts:220
|
This detects whether or not the modal contains scrolling content. To force trigger a detection (ie. on window resize), change or reset the value of the modal content. Use the |
import {
AfterViewInit,
Component,
EventEmitter,
HostListener,
Input,
Output,
ElementRef,
ViewChild,
SimpleChanges,
OnChanges,
Renderer2,
Inject,
OnDestroy
} from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { cycleTabs, getFocusElementList } from "carbon-components-angular/common";
import { BaseModalService } from "./base-modal.service";
/**
* Component to create modals for presenting content.
*
* [See demo](../../?path=/story/components-modal--basic)
*
* Using a modal in your application requires `cds-placeholder` which would generally be
* placed near the end of your app component template (app.component.ts or app.component.html) as:
*
```html
<cds-placeholder></cds-placeholder>
```
*
* A more complete example for `Modal` is given as follows:
*
* Example modal definition:
*
```typescript
@Component({
selector: "app-sample-modal",
template: `
<cds-modal size="xl" (overlaySelected)="closeModal()">
<cds-modal-header (closeSelect)="closeModal()">Header text</cds-modal-header>
<section class="modal-body">
<h1>Sample modal works.</h1>
<button class="btn--icon-link" nPopover="Hello there" title="Popover title" placement="right" appendInline="true">
<svg cdsIcon="info" size="sm"></svg>
</button>
{{modalText}}
</section>
<cds-modal-footer><button cdsButton="primary" (click)="closeModal()">Close</button></cds-modal-footer>
</cds-modal>`,
styleUrls: ["./sample-modal.component.scss"]
})
export class SampleModal extends BaseModal {
modalText: string;
constructor(protected injector: Injector) {
super();
this.modalText = this.injector.get("modalText");
}
}
```
*
* Example of opening the modal:
*
```typescript
@Component({
selector: "app-modal-demo",
template: `
<button cdsButton="primary" (click)="openModal('drill')">Drill-down modal</button>
<cds-placeholder></cds-placeholder>`
})
export class ModalDemo {
openModal() {
this.modalService.create({component: SampleModal, inputs: {modalText: "Hello universe."}});
}
}
```
*/
@Component({
selector: "cds-modal, ibm-modal",
template: `
<cds-overlay
[theme]="theme"
[open]="open"
(overlaySelect)="overlaySelected.emit()">
<div
class="cds--modal-container"
[ngClass]="{
'cds--modal-container--xs': size === 'xs',
'cds--modal-container--sm': size === 'sm',
'cds--modal-container--md': size === 'md',
'cds--modal-container--lg': size === 'lg'
}"
role="dialog"
aria-modal="true"
style="z-index:1;"
[attr.aria-label]="ariaLabel"
#modal>
<ng-content></ng-content>
</div>
</cds-overlay>
`
})
export class Modal implements AfterViewInit, OnChanges, OnDestroy {
/**
* Size of the modal to display.
*/
@Input() size: "xs" | "sm"| "md" | "lg" = "md";
/**
* Classification of the modal.
*/
@Input() theme: "default" | "danger" = "default";
/**
* Label for the modal.
*/
@Input() ariaLabel = "default";
/**
* Controls the visibility of the modal when used directly in a template
*/
@Input() open = false;
/**
* The element that triggers the modal, which should receive focus when the modal closes
*/
@Input() trigger: HTMLElement;
/**
* Specify whether the modal contains scrolling content. This property overrides the automatic
* detection of the existence of scrolling content. Set this property to `true` to force
* overflow indicator to show up or to `false` to force overflow indicator to disappear.
* It is set to `null` by default which indicates not to override automatic detection.
*/
@Input() hasScrollingContent: boolean = null;
/**
* Emits event when click occurs within `n-overlay` element. This is to track click events occurring outside bounds of the `Modal` object.
*/
@Output() overlaySelected = new EventEmitter();
/**
* To emit the closing event of the modal window.
*/
@Output() close = new EventEmitter();
/**
* Maintains a reference to the view DOM element of the `Modal`.
*/
@ViewChild("modal", { static: true }) modal: ElementRef;
/**
* An element should have 'modal-primary-focus' as an attribute to receive initial focus within the `Modal` component.
*/
selectorPrimaryFocus = "[modal-primary-focus]";
/**
* Creates an instance of `Modal`.
*/
constructor(
public modalService: BaseModalService,
@Inject(DOCUMENT) private document: Document,
private renderer: Renderer2
) { }
ngOnChanges({ open, hasScrollingContent }: SimpleChanges) {
if (open) {
if (open.currentValue) {
// `100` is just enough time to allow the modal
// to become visible, so that we can set focus
setTimeout(() => this.focusInitialElement(), 100);
// Prevent scrolling on open
this.renderer.addClass(this.document.body, "cds--body--with-modal-open");
} else if (!open.currentValue) {
// Enable scrolling on close
this.renderer.removeClass(this.document.body, "cds--body--with-modal-open");
} else if (this.trigger) {
this.trigger.focus();
}
}
if (hasScrollingContent) {
this.updateScrollbar();
}
}
/**
* Set document focus to be on the modal component after it is initialized.
*/
ngAfterViewInit() {
this.focusInitialElement();
this.updateScrollbar();
}
/**
* Handle keyboard events to close modal and tab through the content within the modal.
*/
@HostListener("keydown", ["$event"])
handleKeyboardEvent(event: KeyboardEvent) {
switch (event.key) {
case "Escape": {
event.stopImmediatePropagation(); // prevents events being fired for multiple modals if more than 2 open
// Manually close modal
this.open = false;
this.close.emit();
this.modalService.destroy(); // destroy top (latest) modal
break;
}
case "Tab": {
cycleTabs(event, this.modal.nativeElement);
break;
}
}
}
/**
* This detects whether or not the modal contains scrolling content.
*
* To force trigger a detection (ie. on window resize), change or reset the value of the modal content.
*
* Use the `hasScrollingContent` input to manually override the overflow indicator.
*/
get shouldShowScrollbar() {
const modalContent = this.modal ? this.modal.nativeElement.querySelector(".cds--modal-content") : null;
if (modalContent) {
// get rounded value from height to match integer returned from scrollHeight
const modalContentHeight = Math.ceil(modalContent.getBoundingClientRect().height);
const modalContentScrollHeight = modalContent.scrollHeight;
return modalContentScrollHeight > modalContentHeight;
} else {
return false;
}
}
// Remove class preventing scrolling
ngOnDestroy() {
this.renderer.removeClass(this.document.body, "cds--body--with-modal-open");
}
protected focusInitialElement() {
const primaryFocusElement = this.modal.nativeElement.querySelector(this.selectorPrimaryFocus);
if (primaryFocusElement && primaryFocusElement.focus) {
setTimeout(() => primaryFocusElement.focus());
} else if (getFocusElementList(this.modal.nativeElement).length > 0) {
setTimeout(() => getFocusElementList(this.modal.nativeElement)[0].focus());
} else {
setTimeout(() => this.modal.nativeElement.focus());
}
}
private updateScrollbar() {
const modalContent = this.modal ? this.modal.nativeElement.querySelector(".cds--modal-content") : null;
const showScrollbar = this.hasScrollingContent !== null ? this.hasScrollingContent : this.shouldShowScrollbar;
if (modalContent) {
if (showScrollbar) {
this.renderer.addClass(modalContent, "cds--modal-scroll-content");
} else {
this.renderer.removeClass(modalContent, "cds--modal-scroll-content");
}
}
}
}