@ng-bootstrap/ng-bootstrap
Version:
Angular powered Bootstrap
1,325 lines (1,304 loc) • 363 kB
JavaScript
import { ɵɵdefineInjectable, Injectable, ɵɵinject, Directive, TemplateRef, EventEmitter, Input, Output, ContentChildren, Component, ViewEncapsulation, ElementRef, NgZone, ChangeDetectorRef, Optional, Host, NgModule, ChangeDetectionStrategy, Renderer2, forwardRef, Inject, PLATFORM_ID, LOCALE_ID, ViewChild, ContentChild, ViewContainerRef, ComponentFactoryResolver, Injector, ApplicationRef, INJECTOR, RendererFactory2, Attribute, ViewChildren, HostBinding, InjectionToken } from '@angular/core';
import { CommonModule, isPlatformBrowser, getLocaleMonthNames, FormStyle, TranslationWidth, getLocaleDayNames, formatDate, DOCUMENT, getLocaleDayPeriods } from '@angular/common';
import { Observable, EMPTY, of, Subject, fromEvent, timer, race, BehaviorSubject, combineLatest, NEVER, zip, merge } from 'rxjs';
import { endWith, takeUntil, filter, take, map, startWith, distinctUntilChanged, switchMap, tap, withLatestFrom, delay, mergeMap, skip, share } from 'rxjs/operators';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormsModule } from '@angular/forms';
function toInteger(value) {
return parseInt(`${value}`, 10);
}
function toString(value) {
return (value !== undefined && value !== null) ? `${value}` : '';
}
function getValueInRange(value, max, min = 0) {
return Math.max(Math.min(value, max), min);
}
function isString(value) {
return typeof value === 'string';
}
function isNumber(value) {
return !isNaN(toInteger(value));
}
function isInteger(value) {
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
}
function isDefined(value) {
return value !== undefined && value !== null;
}
function padNumber(value) {
if (isNumber(value)) {
return `0${value}`.slice(-2);
}
else {
return '';
}
}
function regExpEscape(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
function hasClassName(element, className) {
return element && element.className && element.className.split &&
element.className.split(/\s+/).indexOf(className) >= 0;
}
if (typeof Element !== 'undefined' && !Element.prototype.closest) {
// Polyfill for ie10+
if (!Element.prototype.matches) {
// IE uses the non-standard name: msMatchesSelector
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
Element.prototype.closest = function (s) {
let el = this;
if (!document.documentElement.contains(el)) {
return null;
}
do {
if (el.matches(s)) {
return el;
}
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
function closest(element, selector) {
if (!selector) {
return null;
}
/*
* In certain browsers (e.g. Edge 44.18362.449.0) HTMLDocument does
* not support `Element.prototype.closest`. To emulate the correct behaviour
* we return null when the method is missing.
*
* Note that in evergreen browsers `closest(document.documentElement, 'html')`
* will return the document element whilst in Edge null will be returned. This
* compromise was deemed good enough.
*/
if (typeof element.closest === 'undefined') {
return null;
}
return element.closest(selector);
}
/**
* Force a browser reflow
* @param element element where to apply the reflow
*/
function reflow(element) {
return (element || document.body).getBoundingClientRect();
}
/**
* Creates an observable where all callbacks are executed inside a given zone
*
* @param zone
*/
function runInZone(zone) {
return (source) => {
return new Observable(observer => {
const onNext = (value) => zone.run(() => observer.next(value));
const onError = (e) => zone.run(() => observer.error(e));
const onComplete = () => zone.run(() => observer.complete());
return source.subscribe(onNext, onError, onComplete);
});
};
}
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
const environment = {
animation: true,
transitionTimerDelayMs: 5,
};
/**
* Global ng-bootstrap config
*
* @since 8.0.0
*/
class NgbConfig {
constructor() {
this.animation = environment.animation;
}
}
NgbConfig.ɵprov = ɵɵdefineInjectable({ factory: function NgbConfig_Factory() { return new NgbConfig(); }, token: NgbConfig, providedIn: "root" });
NgbConfig.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/**
* A configuration service for the [NgbAccordion](#/components/accordion/api#NgbAccordion) component.
*
* You can inject this service, typically in your root component, and customize its properties
* to provide default values for all accordions used in the application.
*/
class NgbAccordionConfig {
constructor(_ngbConfig) {
this._ngbConfig = _ngbConfig;
this.closeOthers = false;
}
get animation() { return (this._animation === undefined) ? this._ngbConfig.animation : this._animation; }
set animation(animation) { this._animation = animation; }
}
NgbAccordionConfig.ɵprov = ɵɵdefineInjectable({ factory: function NgbAccordionConfig_Factory() { return new NgbAccordionConfig(ɵɵinject(NgbConfig)); }, token: NgbAccordionConfig, providedIn: "root" });
NgbAccordionConfig.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
NgbAccordionConfig.ctorParameters = () => [
{ type: NgbConfig }
];
function getTransitionDurationMs(element) {
const { transitionDelay, transitionDuration } = window.getComputedStyle(element);
const transitionDelaySec = parseFloat(transitionDelay);
const transitionDurationSec = parseFloat(transitionDuration);
return (transitionDelaySec + transitionDurationSec) * 1000;
}
const noopFn = () => { };
const ɵ0 = noopFn;
const { transitionTimerDelayMs } = environment;
const runningTransitions = new Map();
const ngbRunTransition = (zone, element, startFn, options) => {
// Getting initial context from options
let context = options.context || {};
// Checking if there are already running transitions on the given element.
const running = runningTransitions.get(element);
if (running) {
switch (options.runningTransition) {
// If there is one running and we want for it to 'continue' to run, we have to cancel the new one.
// We're not emitting any values, but simply completing the observable (EMPTY).
case 'continue':
return EMPTY;
// If there is one running and we want for it to 'stop', we have to complete the running one.
// We're simply completing the running one and not emitting any values and merging newly provided context
// with the one coming from currently running transition.
case 'stop':
zone.run(() => running.transition$.complete());
context = Object.assign(running.context, context);
runningTransitions.delete(element);
}
}
// Running the start function
const endFn = startFn(element, options.animation, context) || noopFn;
// If 'prefer-reduced-motion' is enabled, the 'transition' will be set to 'none'.
// If animations are disabled, we have to emit a value and complete the observable
// In this case we have to call the end function, but can finish immediately by emitting a value,
// completing the observable and executing end functions synchronously.
if (!options.animation || window.getComputedStyle(element).transitionProperty === 'none') {
zone.run(() => endFn());
return of(undefined).pipe(runInZone(zone));
}
// Starting a new transition
const transition$ = new Subject();
const finishTransition$ = new Subject();
const stop$ = transition$.pipe(endWith(true));
runningTransitions.set(element, {
transition$,
complete: () => {
finishTransition$.next();
finishTransition$.complete();
},
context
});
const transitionDurationMs = getTransitionDurationMs(element);
// 1. We have to both listen for the 'transitionend' event and have a 'just-in-case' timer,
// because 'transitionend' event might not be fired in some browsers, if the transitioning
// element becomes invisible (ex. when scrolling, making browser tab inactive, etc.). The timer
// guarantees, that we'll release the DOM element and complete 'ngbRunTransition'.
// 2. We need to filter transition end events, because they might bubble from shorter transitions
// on inner DOM elements. We're only interested in the transition on the 'element' itself.
zone.runOutsideAngular(() => {
const transitionEnd$ = fromEvent(element, 'transitionend').pipe(takeUntil(stop$), filter(({ target }) => target === element));
const timer$ = timer(transitionDurationMs + transitionTimerDelayMs).pipe(takeUntil(stop$));
race(timer$, transitionEnd$, finishTransition$).pipe(takeUntil(stop$)).subscribe(() => {
runningTransitions.delete(element);
zone.run(() => {
endFn();
transition$.next();
transition$.complete();
});
});
});
return transition$.asObservable();
};
const ngbCompleteTransition = (element) => {
var _a;
(_a = runningTransitions.get(element)) === null || _a === void 0 ? void 0 : _a.complete();
};
function measureCollapsingElementHeightPx(element) {
// SSR fix for without injecting the PlatformId
if (typeof navigator === 'undefined') {
return '0px';
}
const { classList } = element;
const hasShownClass = classList.contains('show');
if (!hasShownClass) {
classList.add('show');
}
element.style.height = '';
const height = element.getBoundingClientRect().height + 'px';
if (!hasShownClass) {
classList.remove('show');
}
return height;
}
const ngbCollapsingTransition = (element, animation, context) => {
let { direction, maxHeight } = context;
const { classList } = element;
function setInitialClasses() {
classList.add('collapse');
if (direction === 'show') {
classList.add('show');
}
else {
classList.remove('show');
}
}
// without animations we just need to set initial classes
if (!animation) {
setInitialClasses();
return;
}
// No maxHeight -> running the transition for the first time
if (!maxHeight) {
maxHeight = measureCollapsingElementHeightPx(element);
context.maxHeight = maxHeight;
// Fix the height before starting the animation
element.style.height = direction !== 'show' ? maxHeight : '0px';
classList.remove('collapse');
classList.remove('collapsing');
classList.remove('show');
reflow(element);
// Start the animation
classList.add('collapsing');
}
// Start or revert the animation
element.style.height = direction === 'show' ? maxHeight : '0px';
return () => {
setInitialClasses();
classList.remove('collapsing');
element.style.height = '';
};
};
let nextId = 0;
/**
* A directive that wraps an accordion panel header with any HTML markup and a toggling button
* marked with [`NgbPanelToggle`](#/components/accordion/api#NgbPanelToggle).
* See the [header customization demo](#/components/accordion/examples#header) for more details.
*
* You can also use [`NgbPanelTitle`](#/components/accordion/api#NgbPanelTitle) to customize only the panel title.
*
* @since 4.1.0
*/
class NgbPanelHeader {
constructor(templateRef) {
this.templateRef = templateRef;
}
}
NgbPanelHeader.decorators = [
{ type: Directive, args: [{ selector: 'ng-template[ngbPanelHeader]' },] }
];
NgbPanelHeader.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* A directive that wraps only the panel title with HTML markup inside.
*
* You can also use [`NgbPanelHeader`](#/components/accordion/api#NgbPanelHeader) to customize the full panel header.
*/
class NgbPanelTitle {
constructor(templateRef) {
this.templateRef = templateRef;
}
}
NgbPanelTitle.decorators = [
{ type: Directive, args: [{ selector: 'ng-template[ngbPanelTitle]' },] }
];
NgbPanelTitle.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* A directive that wraps the accordion panel content.
*/
class NgbPanelContent {
constructor(templateRef) {
this.templateRef = templateRef;
}
}
NgbPanelContent.decorators = [
{ type: Directive, args: [{ selector: 'ng-template[ngbPanelContent]' },] }
];
NgbPanelContent.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* A directive that wraps an individual accordion panel with title and collapsible content.
*/
class NgbPanel {
constructor() {
/**
* If `true`, the panel is disabled an can't be toggled.
*/
this.disabled = false;
/**
* An optional id for the panel that must be unique on the page.
*
* If not provided, it will be auto-generated in the `ngb-panel-xxx` format.
*/
this.id = `ngb-panel-${nextId++}`;
this.isOpen = false;
/* A flag to specified that the transition panel classes have been initialized */
this.initClassDone = false;
/* A flag to specified if the panel is currently being animated, to ensure its presence in the dom */
this.transitionRunning = false;
/**
* An event emitted when the panel is shown, after the transition. It has no payload.
*
* @since 8.0.0
*/
this.shown = new EventEmitter();
/**
* An event emitted when the panel is hidden, after the transition. It has no payload.
*
* @since 8.0.0
*/
this.hidden = new EventEmitter();
}
ngAfterContentChecked() {
// We are using @ContentChildren instead of @ContentChild as in the Angular version being used
// only @ContentChildren allows us to specify the {descendants: false} option.
// Without {descendants: false} we are hitting bugs described in:
// https://github.com/ng-bootstrap/ng-bootstrap/issues/2240
this.titleTpl = this.titleTpls.first;
this.headerTpl = this.headerTpls.first;
this.contentTpl = this.contentTpls.first;
}
}
NgbPanel.decorators = [
{ type: Directive, args: [{ selector: 'ngb-panel' },] }
];
NgbPanel.propDecorators = {
disabled: [{ type: Input }],
id: [{ type: Input }],
title: [{ type: Input }],
type: [{ type: Input }],
cardClass: [{ type: Input }],
shown: [{ type: Output }],
hidden: [{ type: Output }],
titleTpls: [{ type: ContentChildren, args: [NgbPanelTitle, { descendants: false },] }],
headerTpls: [{ type: ContentChildren, args: [NgbPanelHeader, { descendants: false },] }],
contentTpls: [{ type: ContentChildren, args: [NgbPanelContent, { descendants: false },] }]
};
/**
* Accordion is a collection of collapsible panels (bootstrap cards).
*
* It can ensure only one panel is opened at a time and allows to customize panel
* headers.
*/
class NgbAccordion {
constructor(config, _element, _ngZone, _changeDetector) {
this._element = _element;
this._ngZone = _ngZone;
this._changeDetector = _changeDetector;
/**
* An array or comma separated strings of panel ids that should be opened **initially**.
*
* For subsequent changes use methods like `expand()`, `collapse()`, etc. and
* the `(panelChange)` event.
*/
this.activeIds = [];
/**
* If `true`, panel content will be detached from DOM and not simply hidden when the panel is collapsed.
*/
this.destroyOnHide = true;
/**
* Event emitted right before the panel toggle happens.
*
* See [NgbPanelChangeEvent](#/components/accordion/api#NgbPanelChangeEvent) for payload details.
*/
this.panelChange = new EventEmitter();
/**
* An event emitted when the expanding animation is finished on the panel. The payload is the panel id.
*
* @since 8.0.0
*/
this.shown = new EventEmitter();
/**
* An event emitted when the collapsing animation is finished on the panel, and before the panel element is removed.
* The payload is the panel id.
*
* @since 8.0.0
*/
this.hidden = new EventEmitter();
this.animation = config.animation;
this.type = config.type;
this.closeOtherPanels = config.closeOthers;
}
/**
* Checks if a panel with a given id is expanded.
*/
isExpanded(panelId) { return this.activeIds.indexOf(panelId) > -1; }
/**
* Expands a panel with a given id.
*
* Has no effect if the panel is already expanded or disabled.
*/
expand(panelId) { this._changeOpenState(this._findPanelById(panelId), true); }
/**
* Expands all panels, if `[closeOthers]` is `false`.
*
* If `[closeOthers]` is `true`, it will expand the first panel, unless there is already a panel opened.
*/
expandAll() {
if (this.closeOtherPanels) {
if (this.activeIds.length === 0 && this.panels.length) {
this._changeOpenState(this.panels.first, true);
}
}
else {
this.panels.forEach(panel => this._changeOpenState(panel, true));
}
}
/**
* Collapses a panel with the given id.
*
* Has no effect if the panel is already collapsed or disabled.
*/
collapse(panelId) { this._changeOpenState(this._findPanelById(panelId), false); }
/**
* Collapses all opened panels.
*/
collapseAll() {
this.panels.forEach((panel) => { this._changeOpenState(panel, false); });
}
/**
* Toggles a panel with the given id.
*
* Has no effect if the panel is disabled.
*/
toggle(panelId) {
const panel = this._findPanelById(panelId);
if (panel) {
this._changeOpenState(panel, !panel.isOpen);
}
}
ngAfterContentChecked() {
// active id updates
if (isString(this.activeIds)) {
this.activeIds = this.activeIds.split(/\s*,\s*/);
}
// update panels open states
this.panels.forEach(panel => { panel.isOpen = !panel.disabled && this.activeIds.indexOf(panel.id) > -1; });
// closeOthers updates
if (this.activeIds.length > 1 && this.closeOtherPanels) {
this._closeOthers(this.activeIds[0], false);
this._updateActiveIds();
}
// Setup the initial classes here
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
this.panels.forEach(panel => {
const panelElement = this._getPanelElement(panel.id);
if (panelElement) {
if (!panel.initClassDone) {
panel.initClassDone = true;
ngbRunTransition(this._ngZone, panelElement, ngbCollapsingTransition, {
animation: false,
runningTransition: 'continue',
context: { direction: panel.isOpen ? 'show' : 'hide' }
});
}
}
else {
// Classes must be initialized next time it will be in the dom
panel.initClassDone = false;
}
});
});
}
_changeOpenState(panel, nextState) {
if (panel != null && !panel.disabled && panel.isOpen !== nextState) {
let defaultPrevented = false;
this.panelChange.emit({ panelId: panel.id, nextState: nextState, preventDefault: () => { defaultPrevented = true; } });
if (!defaultPrevented) {
panel.isOpen = nextState;
panel.transitionRunning = true;
if (nextState && this.closeOtherPanels) {
this._closeOthers(panel.id);
}
this._updateActiveIds();
this._runTransitions(this.animation);
}
}
}
_closeOthers(panelId, enableTransition = true) {
this.panels.forEach(panel => {
if (panel.id !== panelId && panel.isOpen) {
panel.isOpen = false;
panel.transitionRunning = enableTransition;
}
});
}
_findPanelById(panelId) { return this.panels.find(p => p.id === panelId) || null; }
_updateActiveIds() {
this.activeIds = this.panels.filter(panel => panel.isOpen && !panel.disabled).map(panel => panel.id);
}
_runTransitions(animation) {
// detectChanges is performed to ensure that all panels are in the dom (via transitionRunning = true)
// before starting the animation
this._changeDetector.detectChanges();
this.panels.forEach(panel => {
// When panel.transitionRunning is true, the transition needs to be started OR reversed,
// The direction (show or hide) is choosen by each panel.isOpen state
if (panel.transitionRunning) {
const panelElement = this._getPanelElement(panel.id);
ngbRunTransition(this._ngZone, panelElement, ngbCollapsingTransition, {
animation,
runningTransition: 'stop',
context: { direction: panel.isOpen ? 'show' : 'hide' }
}).subscribe(() => {
panel.transitionRunning = false;
const { id } = panel;
if (panel.isOpen) {
panel.shown.emit();
this.shown.emit(id);
}
else {
panel.hidden.emit();
this.hidden.emit(id);
}
});
}
});
}
_getPanelElement(panelId) {
return this._element.nativeElement.querySelector('#' + panelId);
}
}
NgbAccordion.decorators = [
{ type: Component, args: [{
selector: 'ngb-accordion',
exportAs: 'ngbAccordion',
encapsulation: ViewEncapsulation.None,
host: { 'class': 'accordion', 'role': 'tablist', '[attr.aria-multiselectable]': '!closeOtherPanels' },
template: `
<ng-template #t ngbPanelHeader let-panel>
<button class="btn btn-link" [ngbPanelToggle]="panel">
{{panel.title}}<ng-template [ngTemplateOutlet]="panel.titleTpl?.templateRef"></ng-template>
</button>
</ng-template>
<ng-template ngFor let-panel [ngForOf]="panels">
<div [class]="'card ' + (panel.cardClass || '')">
<div role="tab" id="{{panel.id}}-header" [class]="'card-header ' + (panel.type ? 'bg-'+panel.type: type ? 'bg-'+type : '')">
<ng-template [ngTemplateOutlet]="panel.headerTpl?.templateRef || t"
[ngTemplateOutletContext]="{$implicit: panel, opened: panel.isOpen}"></ng-template>
</div>
<div id="{{panel.id}}" role="tabpanel" [attr.aria-labelledby]="panel.id + '-header'"
*ngIf="!destroyOnHide || panel.isOpen || panel.transitionRunning">
<div class="card-body">
<ng-template [ngTemplateOutlet]="panel.contentTpl?.templateRef || null"></ng-template>
</div>
</div>
</div>
</ng-template>
`
},] }
];
NgbAccordion.ctorParameters = () => [
{ type: NgbAccordionConfig },
{ type: ElementRef },
{ type: NgZone },
{ type: ChangeDetectorRef }
];
NgbAccordion.propDecorators = {
panels: [{ type: ContentChildren, args: [NgbPanel,] }],
animation: [{ type: Input }],
activeIds: [{ type: Input }],
closeOtherPanels: [{ type: Input, args: ['closeOthers',] }],
destroyOnHide: [{ type: Input }],
type: [{ type: Input }],
panelChange: [{ type: Output }],
shown: [{ type: Output }],
hidden: [{ type: Output }]
};
/**
* A directive to put on a button that toggles panel opening and closing.
*
* To be used inside the [`NgbPanelHeader`](#/components/accordion/api#NgbPanelHeader)
*
* @since 4.1.0
*/
class NgbPanelToggle {
constructor(accordion, panel) {
this.accordion = accordion;
this.panel = panel;
}
set ngbPanelToggle(panel) {
if (panel) {
this.panel = panel;
}
}
}
NgbPanelToggle.decorators = [
{ type: Directive, args: [{
selector: 'button[ngbPanelToggle]',
host: {
'type': 'button',
'[disabled]': 'panel.disabled',
'[class.collapsed]': '!panel.isOpen',
'[attr.aria-expanded]': 'panel.isOpen',
'[attr.aria-controls]': 'panel.id',
'(click)': 'accordion.toggle(panel.id)'
}
},] }
];
NgbPanelToggle.ctorParameters = () => [
{ type: NgbAccordion },
{ type: NgbPanel, decorators: [{ type: Optional }, { type: Host }] }
];
NgbPanelToggle.propDecorators = {
ngbPanelToggle: [{ type: Input }]
};
const NGB_ACCORDION_DIRECTIVES = [NgbAccordion, NgbPanel, NgbPanelTitle, NgbPanelContent, NgbPanelHeader, NgbPanelToggle];
class NgbAccordionModule {
}
NgbAccordionModule.decorators = [
{ type: NgModule, args: [{ declarations: NGB_ACCORDION_DIRECTIVES, exports: NGB_ACCORDION_DIRECTIVES, imports: [CommonModule] },] }
];
/**
* A configuration service for the [NgbAlert](#/components/alert/api#NgbAlert) component.
*
* You can inject this service, typically in your root component, and customize its properties
* to provide default values for all alerts used in the application.
*/
class NgbAlertConfig {
constructor(_ngbConfig) {
this._ngbConfig = _ngbConfig;
this.dismissible = true;
this.type = 'warning';
}
get animation() { return (this._animation === undefined) ? this._ngbConfig.animation : this._animation; }
set animation(animation) { this._animation = animation; }
}
NgbAlertConfig.ɵprov = ɵɵdefineInjectable({ factory: function NgbAlertConfig_Factory() { return new NgbAlertConfig(ɵɵinject(NgbConfig)); }, token: NgbAlertConfig, providedIn: "root" });
NgbAlertConfig.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
NgbAlertConfig.ctorParameters = () => [
{ type: NgbConfig }
];
const ngbAlertFadingTransition = ({ classList }) => {
classList.remove('show');
};
/**
* Alert is a component to provide contextual feedback messages for user.
*
* It supports several alert types and can be dismissed.
*/
class NgbAlert {
constructor(config, _renderer, _element, _zone) {
this._renderer = _renderer;
this._element = _element;
this._zone = _zone;
/**
* An event emitted when the close button is clicked. It has no payload and only relevant for dismissible alerts.
*
* @since 8.0.0
*/
this.closed = new EventEmitter();
this.dismissible = config.dismissible;
this.type = config.type;
this.animation = config.animation;
}
/**
* Triggers alert closing programmatically (same as clicking on the close button (×)).
*
* The returned observable will emit and be completed once the closing transition has finished.
* If the animations are turned off this happens synchronously.
*
* Alternatively you could listen or subscribe to the `(closed)` output
*
* @since 8.0.0
*/
close() {
const transition = ngbRunTransition(this._zone, this._element.nativeElement, ngbAlertFadingTransition, { animation: this.animation, runningTransition: 'continue' });
transition.subscribe(() => this.closed.emit());
return transition;
}
ngOnChanges(changes) {
const typeChange = changes['type'];
if (typeChange && !typeChange.firstChange) {
this._renderer.removeClass(this._element.nativeElement, `alert-${typeChange.previousValue}`);
this._renderer.addClass(this._element.nativeElement, `alert-${typeChange.currentValue}`);
}
}
ngOnInit() { this._renderer.addClass(this._element.nativeElement, `alert-${this.type}`); }
}
NgbAlert.decorators = [
{ type: Component, args: [{
selector: 'ngb-alert',
exportAs: 'ngbAlert',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: { 'role': 'alert', 'class': 'alert show', '[class.fade]': 'animation', '[class.alert-dismissible]': 'dismissible' },
template: `
<ng-content></ng-content>
<button *ngIf="dismissible" type="button" class="close" aria-label="Close" i18n-aria-label="@@ngb.alert.close"
(click)="close()">
<span aria-hidden="true">×</span>
</button>
`,
styles: ["ngb-alert{display:block}"]
},] }
];
NgbAlert.ctorParameters = () => [
{ type: NgbAlertConfig },
{ type: Renderer2 },
{ type: ElementRef },
{ type: NgZone }
];
NgbAlert.propDecorators = {
animation: [{ type: Input }],
dismissible: [{ type: Input }],
type: [{ type: Input }],
closed: [{ type: Output }]
};
class NgbAlertModule {
}
NgbAlertModule.decorators = [
{ type: NgModule, args: [{ declarations: [NgbAlert], exports: [NgbAlert], imports: [CommonModule], entryComponents: [NgbAlert] },] }
];
class NgbButtonLabel {
}
NgbButtonLabel.decorators = [
{ type: Directive, args: [{
selector: '[ngbButtonLabel]',
host: { '[class.btn]': 'true', '[class.active]': 'active', '[class.disabled]': 'disabled', '[class.focus]': 'focused' }
},] }
];
/**
* Allows to easily create Bootstrap-style checkbox buttons.
*
* Integrates with forms, so the value of a checked button is bound to the underlying form control
* either in a reactive or template-driven way.
*/
class NgbCheckBox {
constructor(_label, _cd) {
this._label = _label;
this._cd = _cd;
/**
* If `true`, the checkbox button will be disabled
*/
this.disabled = false;
/**
* The form control value when the checkbox is checked.
*/
this.valueChecked = true;
/**
* The form control value when the checkbox is unchecked.
*/
this.valueUnChecked = false;
this.onChange = (_) => { };
this.onTouched = () => { };
}
set focused(isFocused) {
this._label.focused = isFocused;
if (!isFocused) {
this.onTouched();
}
}
onInputChange($event) {
const modelToPropagate = $event.target.checked ? this.valueChecked : this.valueUnChecked;
this.onChange(modelToPropagate);
this.onTouched();
this.writeValue(modelToPropagate);
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
setDisabledState(isDisabled) {
this.disabled = isDisabled;
this._label.disabled = isDisabled;
}
writeValue(value) {
this.checked = value === this.valueChecked;
this._label.active = this.checked;
// label won't be updated, if it is inside the OnPush component when [ngModel] changes
this._cd.markForCheck();
}
}
NgbCheckBox.decorators = [
{ type: Directive, args: [{
selector: '[ngbButton][type=checkbox]',
host: {
'[checked]': 'checked',
'[disabled]': 'disabled',
'(change)': 'onInputChange($event)',
'(focus)': 'focused = true',
'(blur)': 'focused = false'
},
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgbCheckBox), multi: true }]
},] }
];
NgbCheckBox.ctorParameters = () => [
{ type: NgbButtonLabel },
{ type: ChangeDetectorRef }
];
NgbCheckBox.propDecorators = {
disabled: [{ type: Input }],
valueChecked: [{ type: Input }],
valueUnChecked: [{ type: Input }]
};
let nextId$1 = 0;
/**
* Allows to easily create Bootstrap-style radio buttons.
*
* Integrates with forms, so the value of a checked button is bound to the underlying form control
* either in a reactive or template-driven way.
*/
class NgbRadioGroup {
constructor() {
this._radios = new Set();
this._value = null;
/**
* Name of the radio group applied to radio input elements.
*
* Will be applied to all radio input elements inside the group,
* unless [`NgbRadio`](#/components/buttons/api#NgbRadio)'s specify names themselves.
*
* If not provided, will be generated in the `ngb-radio-xx` format.
*/
this.name = `ngb-radio-${nextId$1++}`;
this.onChange = (_) => { };
this.onTouched = () => { };
}
get disabled() { return this._disabled; }
set disabled(isDisabled) { this.setDisabledState(isDisabled); }
onRadioChange(radio) {
this.writeValue(radio.value);
this.onChange(radio.value);
}
onRadioValueUpdate() { this._updateRadiosValue(); }
register(radio) { this._radios.add(radio); }
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
setDisabledState(isDisabled) {
this._disabled = isDisabled;
this._updateRadiosDisabled();
}
unregister(radio) { this._radios.delete(radio); }
writeValue(value) {
this._value = value;
this._updateRadiosValue();
}
_updateRadiosValue() { this._radios.forEach((radio) => radio.updateValue(this._value)); }
_updateRadiosDisabled() { this._radios.forEach((radio) => radio.updateDisabled()); }
}
NgbRadioGroup.decorators = [
{ type: Directive, args: [{
selector: '[ngbRadioGroup]',
host: { 'role': 'radiogroup' },
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgbRadioGroup), multi: true }]
},] }
];
NgbRadioGroup.propDecorators = {
name: [{ type: Input }]
};
/**
* A directive that marks an input of type "radio" as a part of the
* [`NgbRadioGroup`](#/components/buttons/api#NgbRadioGroup).
*/
class NgbRadio {
constructor(_group, _label, _renderer, _element, _cd) {
this._group = _group;
this._label = _label;
this._renderer = _renderer;
this._element = _element;
this._cd = _cd;
this._value = null;
this._group.register(this);
this.updateDisabled();
}
/**
* The form control value when current radio button is checked.
*/
set value(value) {
this._value = value;
const stringValue = value ? value.toString() : '';
this._renderer.setProperty(this._element.nativeElement, 'value', stringValue);
this._group.onRadioValueUpdate();
}
/**
* If `true`, current radio button will be disabled.
*/
set disabled(isDisabled) {
this._disabled = isDisabled !== false;
this.updateDisabled();
}
set focused(isFocused) {
if (this._label) {
this._label.focused = isFocused;
}
if (!isFocused) {
this._group.onTouched();
}
}
get checked() { return this._checked; }
get disabled() { return this._group.disabled || this._disabled; }
get value() { return this._value; }
get nameAttr() { return this.name || this._group.name; }
ngOnDestroy() { this._group.unregister(this); }
onChange() { this._group.onRadioChange(this); }
updateValue(value) {
// label won't be updated, if it is inside the OnPush component when [ngModel] changes
if (this.value !== value) {
this._cd.markForCheck();
}
this._checked = this.value === value;
this._label.active = this._checked;
}
updateDisabled() { this._label.disabled = this.disabled; }
}
NgbRadio.decorators = [
{ type: Directive, args: [{
selector: '[ngbButton][type=radio]',
host: {
'[checked]': 'checked',
'[disabled]': 'disabled',
'[name]': 'nameAttr',
'(change)': 'onChange()',
'(focus)': 'focused = true',
'(blur)': 'focused = false'
}
},] }
];
NgbRadio.ctorParameters = () => [
{ type: NgbRadioGroup },
{ type: NgbButtonLabel },
{ type: Renderer2 },
{ type: ElementRef },
{ type: ChangeDetectorRef }
];
NgbRadio.propDecorators = {
name: [{ type: Input }],
value: [{ type: Input, args: ['value',] }],
disabled: [{ type: Input, args: ['disabled',] }]
};
const NGB_BUTTON_DIRECTIVES = [NgbButtonLabel, NgbCheckBox, NgbRadioGroup, NgbRadio];
class NgbButtonsModule {
}
NgbButtonsModule.decorators = [
{ type: NgModule, args: [{ declarations: NGB_BUTTON_DIRECTIVES, exports: NGB_BUTTON_DIRECTIVES },] }
];
/**
* A configuration service for the [NgbCarousel](#/components/carousel/api#NgbCarousel) component.
*
* You can inject this service, typically in your root component, and customize its properties
* to provide default values for all carousels used in the application.
*/
class NgbCarouselConfig {
constructor(_ngbConfig) {
this._ngbConfig = _ngbConfig;
this.interval = 5000;
this.wrap = true;
this.keyboard = true;
this.pauseOnHover = true;
this.pauseOnFocus = true;
this.showNavigationArrows = true;
this.showNavigationIndicators = true;
}
get animation() { return (this._animation === undefined) ? this._ngbConfig.animation : this._animation; }
set animation(animation) { this._animation = animation; }
}
NgbCarouselConfig.ɵprov = ɵɵdefineInjectable({ factory: function NgbCarouselConfig_Factory() { return new NgbCarouselConfig(ɵɵinject(NgbConfig)); }, token: NgbCarouselConfig, providedIn: "root" });
NgbCarouselConfig.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
NgbCarouselConfig.ctorParameters = () => [
{ type: NgbConfig }
];
/**
* Defines the carousel slide transition direction.
*/
var NgbSlideEventDirection;
(function (NgbSlideEventDirection) {
NgbSlideEventDirection["LEFT"] = "left";
NgbSlideEventDirection["RIGHT"] = "right";
})(NgbSlideEventDirection || (NgbSlideEventDirection = {}));
const isBeingAnimated = ({ classList }) => {
return classList.contains('carousel-item-left') || classList.contains('carousel-item-right');
};
const ɵ0$1 = isBeingAnimated;
const removeDirectionClasses = (classList) => {
classList.remove('carousel-item-left');
classList.remove('carousel-item-right');
};
const ɵ1 = removeDirectionClasses;
const removeClasses = (classList) => {
removeDirectionClasses(classList);
classList.remove('carousel-item-prev');
classList.remove('carousel-item-next');
};
const ɵ2 = removeClasses;
const ngbCarouselTransitionIn = (element, animation, { direction }) => {
const { classList } = element;
if (!animation) {
removeDirectionClasses(classList);
removeClasses(classList);
classList.add('active');
return;
}
if (isBeingAnimated(element)) {
// Revert the transition
removeDirectionClasses(classList);
}
else {
// For the 'in' transition, a 'pre-class' is applied to the element to ensure its visibility
classList.add('carousel-item-' + (direction === NgbSlideEventDirection.LEFT ? 'next' : 'prev'));
reflow(element);
classList.add('carousel-item-' + direction);
}
return () => {
removeClasses(classList);
classList.add('active');
};
};
const ngbCarouselTransitionOut = (element, animation, { direction }) => {
const { classList } = element;
if (!animation) {
removeDirectionClasses(classList);
removeClasses(classList);
classList.remove('active');
return;
}
// direction is left or right, depending on the way the slide goes out.
if (isBeingAnimated(element)) {
// Revert the transition
removeDirectionClasses(classList);
}
else {
classList.add('carousel-item-' + direction);
}
return () => {
removeClasses(classList);
classList.remove('active');
};
};
let nextId$2 = 0;
/**
* A directive that wraps the individual carousel slide.
*/
class NgbSlide {
constructor(tplRef) {
this.tplRef = tplRef;
/**
* Slide id that must be unique for the entire document.
*
* If not provided, will be generated in the `ngb-slide-xx` format.
*/
this.id = `ngb-slide-${nextId$2++}`;
/**
* An event emitted when the slide transition is finished
*
* @since 8.0.0
*/
this.slid = new EventEmitter();
}
}
NgbSlide.decorators = [
{ type: Directive, args: [{ selector: 'ng-template[ngbSlide]' },] }
];
NgbSlide.ctorParameters = () => [
{ type: TemplateRef }
];
NgbSlide.propDecorators = {
id: [{ type: Input }],
slid: [{ type: Output }]
};
/**
* Carousel is a component to easily create and control slideshows.
*
* Allows to set intervals, change the way user interacts with the slides and provides a programmatic API.
*/
class NgbCarousel {
constructor(config, _platformId, _ngZone, _cd, _container) {
this._platformId = _platformId;
this._ngZone = _ngZone;
this._cd = _cd;
this._container = _container;
this.NgbSlideEventSource = NgbSlideEventSource;
this._destroy$ = new Subject();
this._interval$ = new BehaviorSubject(0);
this._mouseHover$ = new BehaviorSubject(false);
this._focused$ = new BehaviorSubject(false);
this._pauseOnHover$ = new BehaviorSubject(false);
this._pauseOnFocus$ = new BehaviorSubject(false);
this._pause$ = new BehaviorSubject(false);
this._wrap$ = new BehaviorSubject(false);
/**
* An event emitted just before the slide transition starts.
*
* See [`NgbSlideEvent`](#/components/carousel/api#NgbSlideEvent) for payload details.
*/
this.slide = new EventEmitter();
/**
* An event emitted right after the slide transition is completed.
*
* See [`NgbSlideEvent`](#/components/carousel/api#NgbSlideEvent) for payload details.
*
* @since 8.0.0
*/
this.slid = new EventEmitter();
/*
* Keep the ids of the panels currently transitionning
* in order to allow only the transition revertion
*/
this._transitionIds = null;
this.animation = config.animation;
this.interval = config.interval;
this.wrap = config.wrap;
this.keyboard = config.keyboard;
this.pauseOnHover = config.pauseOnHover;
this.pauseOnFocus = config.pauseOnFocus;
this.showNavigationArrows = config.showNavigationArrows;
this.showNavigationIndicators = config.showNavigationIndicators;
}
/**
* Time in milliseconds before the next slide is shown.
*/
set interval(value) {
this._interval$.next(value);
}
get interval() { return this._interval$.value; }
/**
* If `true`, will 'wrap' the carousel by switching from the last slide back to the first.
*/
set wrap(value) {
this._wrap$.next(value);
}
get wrap() { return this._wrap$.value; }
/**
* If `true`, will pause slide switching when mouse cursor hovers the slide.
*
* @since 2.2.0
*/
set pauseOnHover(value) {
this._pauseOnHover$.next(value);
}
get pauseOnHover() { return this._pauseOnHover$.value; }
/**
* If `true`, will pause slide switching when the focus is inside the carousel.
*/
set pauseOnFocus(value) {
this._pauseOnFocus$.next(value);
}
get pauseOnFocus() { return this._pauseOnFocus$.value; }
set mouseHover(value) { this._mouseHover$.next(value); }
get mouseHover() { return this._mouseHover$.value; }
set focused(value) { this._focused$.next(value); }
get focused() { return this._focused$.value; }
arrowLeft() {
this.focus();
this.prev(NgbSlideEventSource.ARROW_LEFT);
}
arrowRight() {
this.focus();
this.next(NgbSlideEventSource.ARROW_RIGHT);
}
ngAfterContentInit() {
// setInterval() doesn't play well with SSR and protractor,
// so we should run it in the browser and outside Angular
if (isPlatformBrowser(this._platformId)) {
this._ngZone.runOutsideAngular(() => {
const hasNextSlide$ = combineLatest([
this.slide.pipe(map(slideEvent => slideEvent.current), startWith(this.activeId)),
this._wrap$, this.slides.changes.pipe(startWith(null))
])
.pipe(map(([currentSlideId, wrap]) => {
const slideArr = this.slides.toArray();
const currentSlideIdx = this._getSlideIdxById(currentSlideId);
return wrap ? slideArr.length > 1 : currentSlideIdx < slideArr.length - 1;
}), distinctUntilChanged());
combineLatest([
this._pause$, this._pauseOnHover$, this._mouseHover$, this._pauseOnFocus$, this._focused$, this._interval$,
hasNextSlide$
])
.pipe(map(([pause, pauseOnHover, mouseHover, pauseOnFocus, focused, interval, hasNextSlide]) => ((pause || (pauseOnHover && mouseHover) || (pauseOnFocus && focused) || !hasNextSlide) ?
0 :
interval)), distinctUntilChanged(), switchMap(interval => interval > 0 ? timer(interval, interval) : NEVER), takeUntil(this._destroy$))
.subscribe(() => this._ngZone.run(() => this.next(NgbSlideEventSource.TIMER)));
});
}
this.slides.changes.pipe(takeUntil(this._destroy$)).subscribe(() => {
var _a;
(_a = this._transitionIds) === null || _a === void 0 ? void 0 : _a.forEach(id => ngbCompleteTransition(this._getSlideElement(id)));
this._transitionIds = null;
this._cd.markForCheck();
// The following code need to be done asynchronously, after the dom becomes stable,
// otherwise all changes will be undone.
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
for (const { id } of this.slides) {
const element = this._getSlideElement(id);
if (id === this.activeId) {
element.classList.add('active');
}
else {
element.classList.remove('active');
}
}
});
});
}
ngAfterContentChecked() {
let activeSlide = this._getSlideById(this.activeId);
this.activeId = activeSlide ? activeSlide.id : (this.slides.length ? this.slides.first.id : '');
}
ngAfterViewInit() {
// Initialize the 'active' class (not managed by the template)
if (this.activeId) {
const element = this._getSlideElement(this.activeId);
if (element) {
element.classList.add('active');
}
}
}
ngOnDestroy() { this._destroy$.next(); }
/**
* Navigates to a slide with the specified identifier.
*/
select(slideId, source) {
this._cycleToSelected(slideId, this._getSlideEventDirection(this.activeId, slideId), source);
}
/**
* Navigates to the previous slide.
*/
prev(source) {
this._cycleToSelected(this._getPrevSlide(this.activeId), NgbSlideEventDirection.RIGHT, source);
}
/**
* Navigates to the next slide.
*/
next(source) {
this._cycleToSelected(this._getNextSlide(this.activeId), NgbSlideEventDirection.LEFT, source);
}
/**
* Pauses cycling through the slides.
*/
pause() { this._pause$.next(true); }
/**
* Restarts cycling through the slides from left to right.
*/
cycle() { this._pause$.next(false); }
/**
* Set the focus on the carousel.
*/
focus() { this._container.nativeElement.focus(); }
_cycleToSelected(slideIdx, direction, source) {
const transitionIds = this._transitionIds;
if (transitionIds && (transitionIds[0] !== slideIdx || transitionIds[1] !== this.activeId)) {
// Revert prevented
return;
}
let selectedSlide = this._getSlideById(slideIdx);
if (selectedSlide && selectedSlide.id !== this.activeId) {
this._transitionIds = [this.activeId, slideIdx];
this.slide.emit({ prev: this.activeId, current: selectedSlide.id, direction: direction, paused: this._pause$.value, source });
const options = {
animation: this.animation,
runningTransition: 'stop',
context: { direction },
};
const transitions = [];
const activeSlide = this._getSlideById(this.activeId);
if (activeSlide) {