UNPKG

@ng-bootstrap/ng-bootstrap

Version:
1,325 lines (1,304 loc) 363 kB
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">&times;</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) {