gramli-angular-notifier
Version:
A well designed, fully animated, highly customizable, and easy-to-use notification library for your Angular application.
816 lines (802 loc) • 27.3 kB
TypeScript
import * as i0 from '@angular/core';
import { AfterViewInit, EventEmitter, ElementRef, Renderer2, TemplateRef, OnDestroy, ChangeDetectorRef, Provider, ModuleWithProviders, InjectionToken } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import * as i3 from '@angular/common';
/**
* Notifier options
*/
interface NotifierOptions {
animations?: {
enabled?: boolean;
hide?: {
easing?: string;
offset?: number | false;
preset?: string;
speed?: number;
};
overlap?: number | false;
shift?: {
easing?: string;
speed?: number;
};
show?: {
easing?: string;
preset?: string;
speed?: number;
};
};
behaviour?: {
autoHide?: number | false;
onClick?: 'hide' | false;
onMouseover?: 'pauseAutoHide' | 'resetAutoHide' | false;
showDismissButton?: boolean;
stacking?: number | false;
};
position?: {
horizontal?: {
distance?: number;
position?: 'left' | 'middle' | 'right';
};
vertical?: {
distance?: number;
gap?: number;
position?: 'top' | 'bottom';
};
};
theme?: string;
}
/**
* Notifier configuration
*
* The notifier configuration defines what notifications look like, how they behave, and how they get animated. It is a global
* configuration, which means that it only can be set once (at the beginning), and cannot be changed afterwards. Aligning to the world of
* Angular, this configuration can be provided in the root app module - alternatively, a meaningful default configuration will be used.
*/
declare class NotifierConfig implements NotifierOptions {
/**
* Customize animations
*/
animations: {
enabled: boolean;
hide: {
easing: string;
offset: number | false;
preset: string;
speed: number;
};
overlap: number | false;
shift: {
easing: string;
speed: number;
};
show: {
easing: string;
preset: string;
speed: number;
};
};
/**
* Customize behaviour
*/
behaviour: {
autoHide: number | false;
onClick: 'hide' | false;
onMouseover: 'pauseAutoHide' | 'resetAutoHide' | false;
showDismissButton: boolean;
stacking: number | false;
};
/**
* Customize positioning
*/
position: {
horizontal: {
distance: number;
position: 'left' | 'middle' | 'right';
};
vertical: {
distance: number;
gap: number;
position: 'top' | 'bottom';
};
};
/**
* Customize theming
*/
theme: string;
/**
* Constructor
*
* @param [customOptions={}] Custom notifier options, optional
*/
constructor(customOptions?: NotifierOptions);
}
/**
* Notifier action
*
* In general, API calls don't get processed right away. Instead, we have to queue them up in order to prevent simultanious API calls
* interfering with each other. This, at least in theory, is possible at any time, primarily due to overlapping animations.
*
* Technical sidenote:
* An action looks pretty similar to the ones within the Flux / Redux pattern.
*/
interface NotifierAction {
/**
* Action payload containing all information necessary to process the action (optional)
*/
payload?: any;
/**
* Action type
*/
type: 'SHOW' | 'HIDE' | 'HIDE_ALL' | 'HIDE_NEWEST' | 'HIDE_OLDEST';
}
/**
* Notifier service
*
* This service provides access to the public notifier API. Once injected into a component, directive, pipe, service, or any other building
* block of an applications, it can be used to show new notifications, and hide existing ones. Internally, it transforms API calls into
* actions, which then get thrown into the action queue - eventually being processed at the right moment.
*/
declare class NotifierService {
/**
* Notifier queue service
*/
private readonly queueService;
/**
* Notifier configuration
*/
private readonly config;
/**
* Get the notifier configuration
*
* @returns Notifier configuration
*/
getConfig(): NotifierConfig;
/**
* Get the observable for handling actions
*
* @returns Observable of NotifierAction
*/
get actionStream(): Observable<NotifierAction>;
/**
* API: Show a new notification
*
* @param notificationOptions Notification options
*/
show(notificationOptions: NotifierNotificationOptions): void;
/**
* API: Hide a specific notification, given its ID
*
* @param notificationId ID of the notification to hide
*/
hide(notificationId: string): void;
/**
* API: Hide the newest notification
*/
hideNewest(): void;
/**
* API: Hide the oldest notification
*/
hideOldest(): void;
/**
* API: Hide all notifications at once
*/
hideAll(): void;
/**
* API: Shortcut for showing a new notification
*
* @param type Type of the notification
* @param message Message of the notification
* @param [notificationId] Unique ID for the notification (optional)
*/
notify(type: string, message: string, notificationId?: string): void;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<NotifierService>;
}
/**
* Notifier animation data
*
* This interface describes an object containing all information necessary to run an animation, in particular to run an animation with the
* all new (shiny) Web Animations API. When other components or services request data for an animation they have to run, this is the object
* they get back from the animation service.
*
* Technical sidenote:
* Nope, it's not a coincidence - the structure looks similar to the Web Animation API syntax.
*/
interface NotifierAnimationData {
/**
* Animation keyframes; the first index ctonaining changes for animate-in, the second index those for animate-out
*/
keyframes: Array<{
[]: string;
}>;
/**
* Futher animation options
*/
options: {
/**
* Animation duration, in ms
*/
duration: number;
/**
* Animation easing function (comp. CSS easing functions)
*/
easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | string;
/**
* Animation fill mode
*/
fill: 'none' | 'forwards' | 'backwards';
};
}
/**
* Notifier animation service
*/
declare class NotifierAnimationService {
/**
* List of animation presets (currently static)
*/
private readonly animationPresets;
/**
* Constructor
*/
constructor();
/**
* Get animation data
*
* This method generates all data the Web Animations API needs to animate our notification. The result depends on both the animation
* direction (either in or out) as well as the notifications (and its attributes) itself.
*
* @param direction Animation direction, either in or out
* @param notification Notification the animation data should be generated for
* @returns Animation information
*/
getAnimationData(direction: 'show' | 'hide', notification: NotifierNotification): NotifierAnimationData;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierAnimationService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<NotifierAnimationService>;
}
/**
* Notifier timer service
*
* This service acts as a timer, needed due to the still rather limited setTimeout JavaScript API. The timer service can start and stop a
* timer. Furthermore, it can also pause the timer at any time, and resume later on. The timer API workd promise-based.
*/
declare class NotifierTimerService {
/**
* Timestamp (in ms), created in the moment the timer starts
*/
private now;
/**
* Remaining time (in ms)
*/
private remaining;
/**
* Timeout ID, used for clearing the timeout later on
*/
private timerId;
/**
* Promise resolve function, eventually getting called once the timer finishes
*/
private finishPromiseResolver;
/**
* Constructor
*/
constructor();
/**
* Start (or resume) the timer
*
* @param duration Timer duration, in ms
* @returns Promise, resolved once the timer finishes
*/
start(duration: number): Promise<void>;
/**
* Pause the timer
*/
pause(): void;
/**
* Continue the timer
*/
continue(): void;
/**
* Stop the timer
*/
stop(): void;
/**
* Finish up the timeout by resolving the timer promise
*/
private finish;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierTimerService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<NotifierTimerService>;
}
/**
* Notifier notification component
* -------------------------------
* This component is responsible for actually displaying the notification on screen. In addition, it's able to show and hide this
* notification, in particular to animate this notification in and out, as well as shift (move) this notification vertically around.
* Furthermore, the notification component handles all interactions the user has with this notification / component, such as clicks and
* mouse movements.
*/
declare class NotifierNotificationComponent implements AfterViewInit {
/**
* Input: Notification object, contains all details necessary to construct the notification
*/
notification: NotifierNotification;
/**
* Output: Ready event, handles the initialization success by emitting a reference to this notification component
*/
ready: EventEmitter<NotifierNotificationComponent>;
/**
* Output: Dismiss event, handles the click on the dismiss button by emitting the notification ID of this notification component
*/
dismiss: EventEmitter<string>;
/**
* Notifier configuration
*/
readonly config: NotifierConfig;
/**
* Notifier timer service
*/
private readonly timerService;
/**
* Notifier animation service
*/
private readonly animationService;
/**
* Angular renderer, used to preserve the overall DOM abstraction & independence
*/
private readonly renderer;
/**
* Native element reference, used for manipulating DOM properties
*/
private readonly element;
/**
* Current notification height, calculated and cached here (#perfmatters)
*/
private elementHeight;
/**
* Current notification width, calculated and cached here (#perfmatters)
*/
private elementWidth;
/**
* Current notification shift, calculated and cached here (#perfmatters)
*/
private elementShift;
/**
* Constructor
*
* @param elementRef Reference to the component's element
* @param renderer Angular renderer
* @param notifierService Notifier service
* @param notifierTimerService Notifier timer service
* @param notifierAnimationService Notifier animation service
*/
constructor(elementRef: ElementRef, renderer: Renderer2, notifierService: NotifierService, notifierTimerService: NotifierTimerService, notifierAnimationService: NotifierAnimationService);
/**
* Component after view init lifecycle hook, setts up the component and then emits the ready event
*/
ngAfterViewInit(): void;
/**
* Get the notifier config
*
* @returns Notifier configuration
*/
getConfig(): NotifierConfig;
/**
* Get notification element height (in px)
*
* @returns Notification element height (in px)
*/
getHeight(): number;
/**
* Get notification element width (in px)
*
* @returns Notification element height (in px)
*/
getWidth(): number;
/**
* Get notification shift offset (in px)
*
* @returns Notification element shift offset (in px)
*/
getShift(): number;
/**
* Show (animate in) this notification
*
* @returns Promise, resolved when done
*/
show(): Promise<void>;
/**
* Hide (animate out) this notification
*
* @returns Promise, resolved when done
*/
hide(): Promise<void>;
/**
* Shift (move) this notification
*
* @param distance Distance to shift (in px)
* @param shiftToMakePlace Flag, defining in which direction to shift
* @returns Promise, resolved when done
*/
shift(distance: number, shiftToMakePlace: boolean): Promise<void>;
/**
* Handle click on dismiss button
*/
onClickDismiss(): void;
/**
* Handle mouseover over notification area
*/
onNotificationMouseover(): void;
/**
* Handle mouseout from notification area
*/
onNotificationMouseout(): void;
/**
* Handle click on notification area
*/
onNotificationClick(): void;
/**
* Start the auto hide timer (if enabled)
*/
private startAutoHideTimer;
/**
* Pause the auto hide timer (if enabled)
*/
private pauseAutoHideTimer;
/**
* Continue the auto hide timer (if enabled)
*/
private continueAutoHideTimer;
/**
* Stop the auto hide timer (if enabled)
*/
private stopAutoHideTimer;
/**
* Initial notification setup
*/
private setup;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierNotificationComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<NotifierNotificationComponent, "notifier-notification", never, { "notification": { "alias": "notification"; "required": false; }; }, { "ready": "ready"; "dismiss": "dismiss"; }, never, never, false, never>;
}
/**
* Notification
*
* This class describes the structure of a notifiction, including all information it needs to live, and everyone else needs to work with it.
*/
declare class NotifierNotification {
/**
* Unique notification ID, can be set manually to control the notification from outside later on
*/
id: string;
/**
* Notification type, will be used for constructing an appropriate class name
*/
type: string;
/**
* Notification message
*/
message: string;
/**
* The template to customize
* the appearance of the notification
*/
template?: TemplateRef<any>;
/**
* Component reference of this notification, created and set during creation time
*/
component: NotifierNotificationComponent;
/**
* Constructor
*
* @param options Notifier options
*/
constructor(options: NotifierNotificationOptions);
}
/**
* Notifiction options
*
* This interface describes which information are needed to create a new notification, or in other words, which information the external API
* call must provide.
*/
interface NotifierNotificationOptions {
/**
* Notification ID, optional
*/
id?: string;
/**
* Notification type
*/
type: string;
/**
* Notificatin message
*/
message: string;
/**
* The template to customize
* the appearance of the notification
*/
template?: TemplateRef<any>;
}
/**
* Notifier queue service
*
* In general, API calls don't get processed right away. Instead, we have to queue them up in order to prevent simultanious API calls
* interfering with each other. This, at least in theory, is possible at any time. In particular, animations - which potentially overlap -
* can cause changes in JS classes as well as affect the DOM. Therefore, the queue service takes all actions, puts them in a queue, and
* processes them at the right time (which is when the previous action has been processed successfully).
*
* Technical sidenote:
* An action looks pretty similar to the ones within the Flux / Redux pattern.
*/
declare class NotifierQueueService {
/**
* Stream of actions, subscribable from outside
*/
readonly actionStream: Subject<NotifierAction>;
/**
* Queue of actions
*/
private actionQueue;
/**
* Flag, true if some action is currently in progress
*/
private isActionInProgress;
/**
* Constructor
*/
constructor();
/**
* Push a new action to the queue, and try to run it
*
* @param action Action object
*/
push(action: NotifierAction): void;
/**
* Continue with the next action (called when the current action is finished)
*/
continue(): void;
/**
* Try to run the next action in the queue; we skip if there already is some action in progress, or if there is no action left
*/
private tryToRunNextAction;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierQueueService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<NotifierQueueService>;
}
/**
* Notifier container component
* ----------------------------
* This component acts as a wrapper for all notification components; consequently, it is responsible for creating a new notification
* component and removing an existing notification component. Being more precicely, it also handles side effects of those actions, such as
* shifting or even completely removing other notifications as well. Overall, this components handles actions coming from the queue service
* by subscribing to its action stream.
*
* Technical sidenote:
* This component has to be used somewhere in an application to work; it will not inject and create itself automatically, primarily in order
* to not break the Angular AoT compilation. Moreover, this component (and also the notification components) set their change detection
* strategy onPush, which means that we handle change detection manually in order to get the best performance. (#perfmatters)
*/
declare class NotifierContainerComponent implements OnDestroy {
/**
* List of currently somewhat active notifications
*/
notifications: Array<NotifierNotification>;
/**
* Change detector
*/
private readonly changeDetector;
/**
* Notifier queue service
*/
private readonly queueService;
/**
* Notifier configuration
*/
private readonly config;
/**
* Queue service observable subscription (saved for cleanup)
*/
private queueServiceSubscription;
/**
* Promise resolve function reference, temporarily used while the notification child component gets created
*/
private tempPromiseResolver;
/**
* Constructor
*
* @param changeDetector Change detector, used for manually triggering change detection runs
* @param notifierQueueService Notifier queue service
* @param notifierService Notifier service
*/
constructor(changeDetector: ChangeDetectorRef, notifierQueueService: NotifierQueueService, notifierService: NotifierService);
/**
* Component destroyment lifecycle hook, cleans up the observable subsciption
*/
ngOnDestroy(): void;
/**
* Notification identifier, used as the ngFor trackby function
*
* @param index Index
* @param notification Notifier notification
* @returns Notification ID as the unique identnfier
*/
identifyNotification(index: number, notification: NotifierNotification): string;
/**
* Event handler, handles clicks on notification dismiss buttons
*
* @param notificationId ID of the notification to dismiss
*/
onNotificationDismiss(notificationId: string): void;
/**
* Event handler, handles notification ready events
*
* @param notificationComponent Notification component reference
*/
onNotificationReady(notificationComponent: NotifierNotificationComponent): void;
/**
* Handle incoming actions by mapping action types to methods, and then running them
*
* @param action Action object
* @returns Promise, resolved when done
*/
private handleAction;
/**
* Show a new notification
*
* We simply add the notification to the list, and then wait until its properly initialized / created / rendered.
*
* @param action Action object
* @returns Promise, resolved when done
*/
private handleShowAction;
/**
* Continue to show a new notification (after the notification components is initialized / created / rendered).
*
* If this is the first (and thus only) notification, we can simply show it. Otherwhise, if stacking is disabled (or a low value), we
* switch out notifications, in particular we hide the existing one, and then show our new one. Yet, if stacking is enabled, we first
* shift all older notifications, and then show our new notification. In addition, if there are too many notification on the screen,
* we hide the oldest one first. Furthermore, if configured, animation overlapping is applied.
*
* @param notification New notification to show
*/
private continueHandleShowAction;
/**
* Hide an existing notification
*
* Fist, we skip everything if there are no notifications at all, or the given notification does not exist. Then, we hide the given
* notification. If there exist older notifications, we then shift them around to fill the gap. Once both hiding the given notification
* and shifting the older notificaitons is done, the given notification gets finally removed (from the DOM).
*
* @param action Action object, payload contains the notification ID
* @returns Promise, resolved when done
*/
private handleHideAction;
/**
* Hide the oldest notification (bridge to handleHideAction)
*
* @param action Action object
* @returns Promise, resolved when done
*/
private handleHideOldestAction;
/**
* Hide the newest notification (bridge to handleHideAction)
*
* @param action Action object
* @returns Promise, resolved when done
*/
private handleHideNewestAction;
/**
* Hide all notifications at once
*
* @returns Promise, resolved when done
*/
private handleHideAllAction;
/**
* Shift multiple notifications at once
*
* @param notifications List containing the notifications to be shifted
* @param distance Distance to shift (in px)
* @param toMakePlace Flag, defining in which direciton to shift
* @returns Promise, resolved when done
*/
private shiftNotifications;
/**
* Add a new notification to the list of notifications (triggers change detection)
*
* @param notification Notification to add to the list of notifications
*/
private addNotificationToList;
/**
* Remove an existing notification from the list of notifications (triggers change detection)
*
* @param notification Notification to be removed from the list of notifications
*/
private removeNotificationFromList;
/**
* Remove all notifications from the list (triggers change detection)
*/
private removeAllNotificationsFromList;
/**
* Helper: Find a notification in the notification list by a given notification ID
*
* @param notificationId Notification ID, used for finding notification
* @returns Notification, undefined if not found
*/
private findNotificationById;
/**
* Helper: Find a notification's index by a given notification ID
*
* @param notificationId Notification ID, used for finding a notification's index
* @returns Notification index, undefined if not found
*/
private findNotificationIndexById;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierContainerComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<NotifierContainerComponent, "notifier-container", never, {}, {}, never, never, false, never>;
}
/**
* Factory for a notifier configuration with custom options
*
* Sidenote:
* Required as Angular AoT compilation cannot handle dynamic functions; see <https://github.com/angular/angular/issues/11262>.
*
* @param options - Custom notifier options
* @returns - Notifier configuration as result
*/
declare function notifierCustomConfigFactory(options: NotifierOptions): NotifierConfig;
/**
* Factory for a notifier configuration with default options
*
* Sidenote:
* Required as Angular AoT compilation cannot handle dynamic functions; see <https://github.com/angular/angular/issues/11262>.
*
* @returns - Notifier configuration as result
*/
declare function notifierDefaultConfigFactory(): NotifierConfig;
/**
* Provide notifier configuration for standalone applications
*
* This function should be used in the application bootstrap providers (main.ts)
* to configure the notifier globally. Import NotifierModule in components that need it.
*
* @example
* ```typescript
* import { bootstrapApplication } from '@angular/platform-browser';
* import { provideNotifier } from 'angular-notifier';
*
* bootstrapApplication(AppComponent, {
* providers: [provideNotifier({ theme: 'material' })]
* });
*
* @Component({
* standalone: true,
* imports: [NotifierModule], // Just import, config comes from bootstrap
* })
* export class AppComponent {}
* ```
*
* @param [options={}] - Custom notifier options
* @returns - Array of providers for the notifier configuration
*/
declare function provideNotifier(options?: NotifierOptions): Provider[];
/**
* Notifier module
*/
declare class NotifierModule {
/**
* Setup the notifier module with custom providers, in this case with a custom configuration based on the givne options
*
* @param [options={}] - Custom notifier options
* @returns - Notifier module with custom providers
*/
static withConfig(options?: NotifierOptions): ModuleWithProviders<NotifierModule>;
static ɵfac: i0.ɵɵFactoryDeclaration<NotifierModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<NotifierModule, [typeof NotifierContainerComponent, typeof NotifierNotificationComponent], [typeof i3.CommonModule], [typeof NotifierContainerComponent]>;
static ɵinj: i0.ɵɵInjectorDeclaration<NotifierModule>;
}
/**
* Injection Token for notifier options
*/
declare const NotifierOptionsToken: InjectionToken<NotifierOptions>;
/**
* Injection Token for notifier configuration
*/
declare const NotifierConfigToken: InjectionToken<NotifierConfig>;
export { NotifierConfig, NotifierConfigToken, NotifierContainerComponent, NotifierModule, NotifierNotificationComponent, NotifierOptionsToken, NotifierService, notifierCustomConfigFactory, notifierDefaultConfigFactory, provideNotifier };
export type { NotifierOptions };