src/dialog/dialog.directive.ts
A generic directive that can be inherited from to create dialogs (for example, a tooltip or popover)
This class contains the relevant initialization code, specific templates, options, and additional inputs should be specified in the derived class.
NOTE: All child classes should add DialogService
as a provider, otherwise they will lose context that
the service relies on.
OnInit
OnDestroy
OnChanges
Providers |
DialogService
|
Selector | [cdsDialog], [ibmDialog] |
exportAs | dialog |
Properties |
|
Methods |
|
Inputs |
Outputs |
HostBindings |
Accessors |
constructor(elementRef: ElementRef, viewContainerRef: ViewContainerRef, dialogService: DialogService, eventService: EventService)
|
|||||||||||||||
Defined in src/dialog/dialog.directive.ts:129
|
|||||||||||||||
Creates an instance of DialogDirective.
Parameters :
|
appendInline | |
Type : boolean
|
|
Default value : false
|
|
Defined in src/dialog/dialog.directive.ts:86
|
|
Set to |
cdsDialog | |
Type : string | TemplateRef<any>
|
|
Defined in src/dialog/dialog.directive.ts:52
|
closeTrigger | |
Type : "mouseout" | "mouseleave"
|
|
Default value : "mouseleave"
|
|
Defined in src/dialog/dialog.directive.ts:66
|
|
Defines how the Dialog close event is triggered. See here
for more on the difference between Defaults to |
data | |
Type : {}
|
|
Default value : {}
|
|
Defined in src/dialog/dialog.directive.ts:90
|
|
Optional data for templates |
disabled | |
Type : boolean
|
|
Default value : false
|
|
Defined in src/dialog/dialog.directive.ts:96
|
|
This prevents the dialog from being toggled |
gap | |
Type : number
|
|
Default value : 0
|
|
Defined in src/dialog/dialog.directive.ts:82
|
|
Spacing between the dialog and it's triggering element |
ibmDialog | |
Type : string | TemplateRef
|
|
Defined in src/dialog/dialog.directive.ts:48
|
isOpen | |
Type : boolean
|
|
Default value : false
|
|
Defined in src/dialog/dialog.directive.ts:92
|
offset | |
Type : literal type
|
|
Defined in src/dialog/dialog.directive.ts:74
|
|
This specifies any vertical and horizontal offset for the position of the dialog |
placement | |
Type : string
|
|
Default value : "left"
|
|
Defined in src/dialog/dialog.directive.ts:70
|
|
Placement of the dialog, usually relative to the element the directive is on. |
shouldClose | |
Type : function
|
|
Defined in src/dialog/dialog.directive.ts:100
|
|
This input allows explicit control over how the dialog should close |
title | |
Type : string
|
|
Default value : ""
|
|
Defined in src/dialog/dialog.directive.ts:43
|
|
Title for the dialog |
trigger | |
Type : "click" | "hover" | "mouseenter"
|
|
Default value : "click"
|
|
Defined in src/dialog/dialog.directive.ts:57
|
|
Defines how the Dialog is triggered.(Hover and click behave the same on mobile - both respond to a single tap).
Do not add focusable elements if trigger is |
wrapperClass | |
Type : string
|
|
Defined in src/dialog/dialog.directive.ts:78
|
|
Classes to add to the dialog container |
isOpenChange | |
Type : EventEmitter
|
|
Defined in src/dialog/dialog.directive.ts:116
|
|
Emits an event when the state of |
onClose | |
Type : EventEmitter<any>
|
|
Defined in src/dialog/dialog.directive.ts:108
|
|
Emits an event when the dialog is closed |
onOpen | |
Type : EventEmitter<any>
|
|
Defined in src/dialog/dialog.directive.ts:112
|
|
Emits an event when the dialog is opened |
attr.aria-haspopup |
Type : boolean
|
Default value : true
|
Defined in src/dialog/dialog.directive.ts:119
|
attr.aria-owns |
Type : string
|
Defined in src/dialog/dialog.directive.ts:120
|
attr.role |
Type : string
|
Default value : "button"
|
Defined in src/dialog/dialog.directive.ts:118
|
close | ||||||||
close(meta: CloseMeta)
|
||||||||
Defined in src/dialog/dialog.directive.ts:305
|
||||||||
Helper method to close the dialogRef.
Parameters :
Returns :
void
|
ngOnChanges | ||||||
ngOnChanges(changes: SimpleChanges)
|
||||||
Defined in src/dialog/dialog.directive.ts:148
|
||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Defined in src/dialog/dialog.directive.ts:252
|
When the host dies, kill the popover.
Returns :
void
|
ngOnInit |
ngOnInit()
|
Defined in src/dialog/dialog.directive.ts:185
|
Sets the config object and binds events for hovering or clicking before running code from child class.
Returns :
void
|
Protected onDialogChanges | ||||||
onDialogChanges(_changes: SimpleChanges)
|
||||||
Defined in src/dialog/dialog.directive.ts:321
|
||||||
Empty method for child to override and specify additional on changes steps. run after DialogDirective completes it's ngOnChanges.
Parameters :
Returns :
void
|
Protected onDialogInit |
onDialogInit()
|
Defined in src/dialog/dialog.directive.ts:315
|
Empty method for child classes to override and specify additional init steps. Run after DialogDirective completes it's ngOnInit.
Returns :
void
|
open | ||||
open(component?)
|
||||
Defined in src/dialog/dialog.directive.ts:263
|
||||
Helper method to call dialogService 'open'.
Parameters :
Returns :
ComponentRef<Dialog>
|
toggle | ||||||||
toggle(meta: CloseMeta)
|
||||||||
Defined in src/dialog/dialog.directive.ts:294
|
||||||||
Helper method to toggle the open state of the dialog
Parameters :
Returns :
void
|
Protected updateConfig |
updateConfig()
|
Defined in src/dialog/dialog.directive.ts:323
|
Returns :
void
|
dialogConfig |
Type : DialogConfig
|
Defined in src/dialog/dialog.directive.ts:104
|
Config object passed to the rendered component |
Static dialogCounter |
Type : number
|
Default value : 0
|
Defined in src/dialog/dialog.directive.ts:39
|
Protected dialogRef |
Type : ComponentRef<Dialog>
|
Defined in src/dialog/dialog.directive.ts:127
|
Keeps a reference to the currently opened dialog |
hasPopup |
Default value : true
|
Decorators :
@HostBinding('attr.aria-haspopup')
|
Defined in src/dialog/dialog.directive.ts:119
|
role |
Type : string
|
Default value : "button"
|
Decorators :
@HostBinding('attr.role')
|
Defined in src/dialog/dialog.directive.ts:118
|
Private subscriptions |
Type : Subscription[]
|
Default value : []
|
Defined in src/dialog/dialog.directive.ts:129
|
ibmDialog | ||||||
setibmDialog(body: string | TemplateRef
|
||||||
Defined in src/dialog/dialog.directive.ts:48
|
||||||
Dialog body content.
Parameters :
Returns :
void
|
ariaOwns |
getariaOwns()
|
Defined in src/dialog/dialog.directive.ts:120
|
import {
Directive,
Input,
Output,
EventEmitter,
OnInit,
OnDestroy,
ElementRef,
TemplateRef,
ViewContainerRef,
OnChanges,
HostBinding,
SimpleChanges,
ComponentRef
} from "@angular/core";
import { DialogService } from "./dialog.service";
import { CloseMeta, CloseReasons, DialogConfig } from "./dialog-config.interface";
import { EventService } from "carbon-components-angular/utils";
import { Dialog } from "./dialog.component";
import { fromEvent, Subscription } from "rxjs";
/**
* A generic directive that can be inherited from to create dialogs (for example, a tooltip or popover)
*
* This class contains the relevant initialization code, specific templates, options, and additional inputs
* should be specified in the derived class.
*
* NOTE: All child classes should add `DialogService` as a provider, otherwise they will lose context that
* the service relies on.
*/
@Directive({
selector: "[cdsDialog], [ibmDialog]",
exportAs: "dialog",
providers: [
DialogService
]
})
export class DialogDirective implements OnInit, OnDestroy, OnChanges {
static dialogCounter = 0;
/**
* Title for the dialog
*/
@Input() title = "";
/**
* @deprecated as of v5, use `cdsDialog` instead
* Dialog body content.
*/
@Input() set ibmDialog(body: string | TemplateRef<any>) {
this.cdsDialog = body;
}
@Input() cdsDialog: string | TemplateRef<any>;
/**
* Defines how the Dialog is triggered.(Hover and click behave the same on mobile - both respond to a single tap).
* Do not add focusable elements if trigger is `hover` or `mouseenter`.
*/
@Input() trigger: "click" | "hover" | "mouseenter" = "click";
/**
* Defines how the Dialog close event is triggered.
*
* [See here](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseleave_event)
* for more on the difference between `mouseleave` and `mouseout`.
*
* Defaults to `click` when `trigger` is set to `click`.
*/
@Input() closeTrigger: "mouseout" | "mouseleave" = "mouseleave";
/**
* Placement of the dialog, usually relative to the element the directive is on.
*/
@Input() placement = "left";
/**
* This specifies any vertical and horizontal offset for the position of the dialog
*/
@Input() offset: { x: number, y: number };
/**
* Classes to add to the dialog container
*/
@Input() wrapperClass: string;
/**
* Spacing between the dialog and it's triggering element
*/
@Input() gap = 0;
/**
* Set to `true` to open the dialog next to the triggering component
*/
@Input() appendInline = false;
/**
* Optional data for templates
*/
@Input() data = {};
@Input() @HostBinding("attr.aria-expanded") isOpen = false;
/**
* This prevents the dialog from being toggled
*/
@Input() disabled = false;
/**
* This input allows explicit control over how the dialog should close
*/
@Input() shouldClose: (meta: CloseMeta) => boolean;
/**
* Config object passed to the rendered component
*/
dialogConfig: DialogConfig;
/**
* Emits an event when the dialog is closed
*/
@Output() onClose: EventEmitter<any> = new EventEmitter();
/**
* Emits an event when the dialog is opened
*/
@Output() onOpen: EventEmitter<any> = new EventEmitter();
/**
* Emits an event when the state of `isOpen` changes. Allows `isOpen` to be double bound
*/
@Output() isOpenChange = new EventEmitter<boolean>();
@HostBinding("attr.role") role = "button";
@HostBinding("attr.aria-haspopup") hasPopup = true;
@HostBinding("attr.aria-owns") get ariaOwns(): string {
return this.isOpen ? this.dialogConfig.compID : null;
}
/**
* Keeps a reference to the currently opened dialog
*/
protected dialogRef: ComponentRef<Dialog>;
private subscriptions: Subscription[] = [];
/**
* Creates an instance of DialogDirective.
* @param elementRef
* @param viewContainerRef
* @param dialogService
* @param eventService
*/
constructor(
protected elementRef: ElementRef,
protected viewContainerRef: ViewContainerRef,
protected dialogService: DialogService,
/**
* Deprecated as of v5
*/
protected eventService: EventService
) {}
ngOnChanges(changes: SimpleChanges) {
// set the config object (this can [and should!] be added to in child classes depending on what they need)
this.dialogConfig = {
title: this.title,
content: this.cdsDialog,
placement: this.placement,
parentRef: this.elementRef,
gap: this.gap,
trigger: this.trigger,
closeTrigger: this.closeTrigger,
shouldClose: this.shouldClose || (() => true),
appendInline: this.appendInline,
wrapperClass: this.wrapperClass,
data: this.data,
offset: this.offset,
disabled: this.disabled
};
if (changes.isOpen) {
if (changes.isOpen.currentValue) {
this.open();
} else if (!changes.isOpen.firstChange) {
this.close({
reason: CloseReasons.programmatic
});
}
}
// Run any code a child class may need.
this.onDialogChanges(changes);
this.updateConfig();
}
/**
* Sets the config object and binds events for hovering or clicking before
* running code from child class.
*/
ngOnInit() {
// fix for safari hijacking clicks
this.dialogService.singletonClickListen();
const element: HTMLElement = this.elementRef.nativeElement;
this.subscriptions.push(
fromEvent(element, "keydown").subscribe((event: KeyboardEvent) => {
if (event.target === this.dialogConfig.parentRef.nativeElement &&
(event.key === "Tab" || event.key === "Tab" && event.shiftKey) ||
event.key === "Escape") {
this.close({
reason: CloseReasons.interaction,
target: event.target
});
}
})
);
// bind events for hovering or clicking the host
if (this.trigger === "hover" || this.trigger === "mouseenter") {
this.subscriptions.push(
fromEvent(element, "mouseenter").subscribe(() => this.open()),
fromEvent(element, this.closeTrigger).subscribe((event) => {
this.close({
reason: CloseReasons.interaction,
target: event.target
});
}),
fromEvent(element, "focus").subscribe(() => this.open()),
fromEvent(element, "blur").subscribe((event) => {
this.close({
reason: CloseReasons.interaction,
target: event.target
});
})
);
} else {
this.subscriptions.push(
fromEvent(element, "click").subscribe((event) => {
this.toggle({
reason: CloseReasons.interaction,
target: event.target
});
}),
fromEvent(element, "keydown").subscribe((event: KeyboardEvent) => {
if (event.key === "Enter" || event.key === " ") {
setTimeout(() => {
this.open();
});
}
})
);
}
DialogDirective.dialogCounter++;
this.dialogConfig.compID = "dialog-" + DialogDirective.dialogCounter;
// run any code a child class may need
this.onDialogInit();
this.updateConfig();
}
/**
* When the host dies, kill the popover.
* - Useful for use in a modal or similar.
*/
ngOnDestroy() {
this.close({
reason: CloseReasons.destroyed
});
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
}
/**
* Helper method to call dialogService 'open'.
* - Enforce accessibility by updating an aria attr for nativeElement.
*/
open(component?) {
// don't allow dialogs to be opened if they're already open
if (this.dialogRef || this.disabled) { return; }
// actually open the dialog, emit events, and set the open state
this.dialogRef = this.dialogService.open(this.viewContainerRef, this.dialogConfig, component);
this.isOpen = true;
this.onOpen.emit();
this.isOpenChange.emit(true);
// Handles emitting all the close events to clean everything up
// Also enforce accessibility on close by updating an aria attr on the nativeElement.
const subscription = this.dialogRef.instance.close.subscribe((meta: CloseMeta) => {
if (!this.dialogRef) { return; }
if (this.dialogConfig.shouldClose && this.dialogConfig.shouldClose(meta)) {
// close the dialog, emit events, and clear out the open states
this.dialogService.close(this.dialogRef);
this.dialogRef = null;
this.isOpen = false;
this.onClose.emit();
this.isOpenChange.emit(false);
subscription.unsubscribe();
}
});
return this.dialogRef;
}
/**
* Helper method to toggle the open state of the dialog
*/
toggle(meta: CloseMeta = { reason: CloseReasons.interaction }) {
if (!this.isOpen) {
this.open();
} else {
this.close(meta);
}
}
/**
* Helper method to close the dialogRef.
*/
close(meta: CloseMeta = { reason: CloseReasons.interaction }) {
if (this.dialogRef) {
this.dialogRef.instance.doClose(meta);
}
}
/**
* Empty method for child classes to override and specify additional init steps.
* Run after DialogDirective completes it's ngOnInit.
*/
protected onDialogInit() {}
/**
* Empty method for child to override and specify additional on changes steps.
* run after DialogDirective completes it's ngOnChanges.
*/
protected onDialogChanges(_changes: SimpleChanges) {}
protected updateConfig() {}
}