@ng-bootstrap/ng-bootstrap
Version:
Angular powered Bootstrap
1,103 lines (1,092 loc) • 568 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Directive, EventEmitter, Input, Output, ContentChildren, forwardRef, Inject, Optional, Host, Component, ViewEncapsulation, inject, ApplicationRef, ElementRef, TemplateRef, ContentChild, NgModule, ChangeDetectionStrategy, PLATFORM_ID, LOCALE_ID, ViewChild, Injector, EnvironmentInjector, createComponent, Attribute, ViewChildren, HostBinding, ChangeDetectorRef, NgZone, DestroyRef, InjectionToken } from '@angular/core';
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, finalize } from 'rxjs/operators';
import { NgFor, NgTemplateOutlet, NgIf, isPlatformBrowser, getLocaleMonthNames, FormStyle, TranslationWidth, getLocaleDayNames, formatDate, DOCUMENT, PercentPipe, getLocaleDayPeriods } from '@angular/common';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { flip, preventOverflow, arrow, createPopperLite, offset } from '@popperjs/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
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 isPromise(v) {
return v && v.then;
}
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);
}
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 next = (value) => zone.run(() => observer.next(value));
const error = (e) => zone.run(() => observer.error(e));
const complete = () => zone.run(() => observer.complete());
return source.subscribe({ next, error, complete });
});
};
}
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
/**
* Returns the active element in the given root.
* If the active element is inside a shadow root, it is searched recursively.
*/
function getActiveElement(root = document) {
const activeEl = root?.activeElement;
if (!activeEl) {
return null;
}
return activeEl.shadowRoot ? getActiveElement(activeEl.shadowRoot) : activeEl;
}
function getTransitionDurationMs(element) {
const { transitionDelay, transitionDuration } = window.getComputedStyle(element);
const transitionDelaySec = parseFloat(transitionDelay);
const transitionDurationSec = parseFloat(transitionDuration);
return (transitionDelaySec + transitionDurationSec) * 1000;
}
const environment = {
animation: true,
transitionTimerDelayMs: 5,
};
const 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) => {
runningTransitions.get(element)?.complete();
};
function measureCollapsingElementDimensionPx(element, dimension) {
// 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[dimension] = '';
const dimensionSize = element.getBoundingClientRect()[dimension] + 'px';
if (!hasShownClass) {
classList.remove('show');
}
return dimensionSize;
}
const ngbCollapsingTransition = (element, animation, context) => {
let { direction, maxSize, dimension } = 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 (!maxSize) {
maxSize = measureCollapsingElementDimensionPx(element, dimension);
context.maxSize = maxSize;
// Fix the height before starting the animation
element.style[dimension] = direction !== 'show' ? maxSize : '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[dimension] = direction === 'show' ? maxSize : '0px';
return () => {
setInitialClasses();
classList.remove('collapsing');
element.style[dimension] = '';
};
};
/**
* Global ng-bootstrap config
*
* @since 8.0.0
*/
class NgbConfig {
constructor() {
this.animation = environment.animation;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbConfig, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbConfig, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* A configuration service for the [`NgbAccordionDirective`](#/components/accordion/api#NgbAccordionDirective).
*
* 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;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionConfig, deps: [{ token: NgbConfig }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionConfig, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionConfig, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () { return [{ type: NgbConfig }]; } });
/* eslint-disable deprecation/deprecation */
let nextId$4 = 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
* @deprecated 14.1.0
*/
class NgbPanelHeader {
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelHeader, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbPanelHeader, isStandalone: true, selector: "ng-template[ngbPanelHeader]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelHeader, decorators: [{
type: Directive,
args: [{ selector: 'ng-template[ngbPanelHeader]', standalone: true }]
}], ctorParameters: function () { return [{ type: i0.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.
*
* @deprecated 14.1.0
*/
class NgbPanelTitle {
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelTitle, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbPanelTitle, isStandalone: true, selector: "ng-template[ngbPanelTitle]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelTitle, decorators: [{
type: Directive,
args: [{ selector: 'ng-template[ngbPanelTitle]', standalone: true }]
}], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } });
/**
* A directive that wraps the accordion panel content.
*
* @deprecated 14.1.0
*/
class NgbPanelContent {
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelContent, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbPanelContent, isStandalone: true, selector: "ng-template[ngbPanelContent]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelContent, decorators: [{
type: Directive,
args: [{ selector: 'ng-template[ngbPanelContent]', standalone: true }]
}], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } });
/**
* A directive that wraps an individual accordion panel with title and collapsible content.
*
* @deprecated 14.1.0
*/
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$4++}`;
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;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbPanel, isStandalone: true, selector: "ngb-panel", inputs: { disabled: "disabled", id: "id", title: "title", type: "type", cardClass: "cardClass" }, outputs: { shown: "shown", hidden: "hidden" }, queries: [{ propertyName: "titleTpls", predicate: NgbPanelTitle }, { propertyName: "headerTpls", predicate: NgbPanelHeader }, { propertyName: "contentTpls", predicate: NgbPanelContent }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanel, decorators: [{
type: Directive,
args: [{ selector: 'ngb-panel', standalone: true }]
}], 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 }]
}] } });
class NgbRefDirective {
constructor(_El) {
this._El = _El;
this.ngbRef = new EventEmitter();
}
ngOnInit() {
this.ngbRef.emit(this._El.nativeElement);
}
ngOnDestroy() {
this.ngbRef.emit(null);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbRefDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbRefDirective, isStandalone: true, selector: "[ngbRef]", outputs: { ngbRef: "ngbRef" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbRefDirective, decorators: [{
type: Directive,
args: [{ selector: '[ngbRef]', standalone: true }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { ngbRef: [{
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
* @deprecated 14.1.0
*/
class NgbPanelToggle {
set ngbPanelToggle(panel) {
if (panel) {
this.panel = panel;
}
}
constructor(accordion, panel) {
this.accordion = accordion;
this.panel = panel;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelToggle, deps: [{ token: forwardRef(() => NgbAccordion) }, { token: NgbPanel, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbPanelToggle, isStandalone: true, selector: "button[ngbPanelToggle]", inputs: { ngbPanelToggle: "ngbPanelToggle" }, host: { attributes: { "type": "button" }, listeners: { "click": "accordion.toggle(panel.id)" }, properties: { "disabled": "panel.disabled", "class.collapsed": "!panel.isOpen", "attr.aria-expanded": "panel.isOpen", "attr.aria-controls": "panel.id" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbPanelToggle, decorators: [{
type: Directive,
args: [{
selector: 'button[ngbPanelToggle]',
standalone: true,
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)',
},
}]
}], ctorParameters: function () { return [{ type: NgbAccordion, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordion)]
}] }, { type: NgbPanel, decorators: [{
type: Optional
}, {
type: Host
}] }]; }, propDecorators: { ngbPanelToggle: [{
type: Input
}] } });
/**
* 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.
*
* @deprecated 14.1.0
*/
class NgbAccordion {
constructor(config, _ngZone, _changeDetector) {
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 = panel.panelDiv;
if (panelElement) {
if (!panel.initClassDone) {
panel.initClassDone = true;
ngbRunTransition(this._ngZone, panelElement, ngbCollapsingTransition, {
animation: false,
runningTransition: 'continue',
context: { direction: panel.isOpen ? 'show' : 'hide', dimension: 'height' },
});
}
}
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 = panel.panelDiv;
ngbRunTransition(this._ngZone, panelElement, ngbCollapsingTransition, {
animation,
runningTransition: 'stop',
context: { direction: panel.isOpen ? 'show' : 'hide', dimension: 'height' },
}).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);
}
});
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordion, deps: [{ token: NgbAccordionConfig }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordion, isStandalone: true, selector: "ngb-accordion", inputs: { animation: "animation", activeIds: "activeIds", closeOtherPanels: ["closeOthers", "closeOtherPanels"], destroyOnHide: "destroyOnHide", type: "type" }, outputs: { panelChange: "panelChange", shown: "shown", hidden: "hidden" }, host: { attributes: { "role": "tablist" }, properties: { "attr.aria-multiselectable": "!closeOtherPanels" }, classAttribute: "accordion" }, queries: [{ propertyName: "panels", predicate: NgbPanel }], exportAs: ["ngbAccordion"], ngImport: i0, template: `
<ng-template #t ngbPanelHeader let-panel>
<button class="accordion-button" [ngbPanelToggle]="panel">
{{ panel.title }}
<ng-template [ngTemplateOutlet]="panel.titleTpl?.templateRef"></ng-template>
</button>
</ng-template>
<ng-template ngFor let-panel [ngForOf]="panels">
<div [class]="'accordion-item ' + (panel.cardClass || '')">
<div
role="tab"
id="{{ panel.id }}-header"
[class]="'accordion-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 }}"
(ngbRef)="panel.panelDiv = $event"
role="tabpanel"
[attr.aria-labelledby]="panel.id + '-header'"
*ngIf="!destroyOnHide || panel.isOpen || panel.transitionRunning"
>
<div class="accordion-body">
<ng-template [ngTemplateOutlet]="panel.contentTpl?.templateRef || null"></ng-template>
</div>
</div>
</div>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPanelToggle, selector: "button[ngbPanelToggle]", inputs: ["ngbPanelToggle"] }, { kind: "directive", type: NgbRefDirective, selector: "[ngbRef]", outputs: ["ngbRef"] }, { kind: "directive", type: NgbPanelHeader, selector: "ng-template[ngbPanelHeader]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordion, decorators: [{
type: Component,
args: [{
selector: 'ngb-accordion',
exportAs: 'ngbAccordion',
standalone: true,
imports: [NgFor, NgTemplateOutlet, NgbPanelToggle, NgbRefDirective, NgbPanelHeader, NgIf],
encapsulation: ViewEncapsulation.None,
host: { class: 'accordion', role: 'tablist', '[attr.aria-multiselectable]': '!closeOtherPanels' },
template: `
<ng-template #t ngbPanelHeader let-panel>
<button class="accordion-button" [ngbPanelToggle]="panel">
{{ panel.title }}
<ng-template [ngTemplateOutlet]="panel.titleTpl?.templateRef"></ng-template>
</button>
</ng-template>
<ng-template ngFor let-panel [ngForOf]="panels">
<div [class]="'accordion-item ' + (panel.cardClass || '')">
<div
role="tab"
id="{{ panel.id }}-header"
[class]="'accordion-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 }}"
(ngbRef)="panel.panelDiv = $event"
role="tabpanel"
[attr.aria-labelledby]="panel.id + '-header'"
*ngIf="!destroyOnHide || panel.isOpen || panel.transitionRunning"
>
<div class="accordion-body">
<ng-template [ngTemplateOutlet]="panel.contentTpl?.templateRef || null"></ng-template>
</div>
</div>
</div>
</ng-template>
`,
}]
}], ctorParameters: function () { return [{ type: NgbAccordionConfig }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, 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 configuration service for the [NgbCollapse](#/components/collapse/api#NgbCollapse) component.
*
* You can inject this service, typically in your root component, and customize its properties
* to provide default values for all collapses used in the application.
*/
class NgbCollapseConfig {
constructor(_ngbConfig) {
this._ngbConfig = _ngbConfig;
this.horizontal = false;
}
get animation() {
return this._animation === undefined ? this._ngbConfig.animation : this._animation;
}
set animation(animation) {
this._animation = animation;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbCollapseConfig, deps: [{ token: NgbConfig }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbCollapseConfig, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbCollapseConfig, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () { return [{ type: NgbConfig }]; } });
/**
* A directive to provide a simple way of hiding and showing elements on the
* page.
*/
class NgbCollapse {
/**
* If `true`, will collapse the element or show it otherwise.
*/
set collapsed(isCollapsed) {
if (this._isCollapsed !== isCollapsed) {
this._isCollapsed = isCollapsed;
if (this._afterInit) {
this._runTransitionWithEvents(isCollapsed, this.animation);
}
}
}
constructor(_element, config, _zone) {
this._element = _element;
this._zone = _zone;
/**
* Flag used to track if the collapse setter is invoked during initialization
* or not. This distinction is made in order to avoid running the transition during initialization.
*/
this._afterInit = false;
this._isCollapsed = false;
this.ngbCollapseChange = new EventEmitter();
/**
* An event emitted when the collapse element is shown, after the transition.
* It has no payload.
*
* @since 8.0.0
*/
this.shown = new EventEmitter();
/**
* An event emitted when the collapse element is hidden, after the transition.
* It has no payload.
*
* @since 8.0.0
*/
this.hidden = new EventEmitter();
this.animation = config.animation;
this.horizontal = config.horizontal;
}
ngOnInit() {
this._runTransition(this._isCollapsed, false);
this._afterInit = true;
}
/**
* Triggers collapsing programmatically.
*
* If there is a collapsing transition running already, it will be reversed.
* If the animations are turned off this happens synchronously.
*
* @since 8.0.0
*/
toggle(open = this._isCollapsed) {
this.collapsed = !open;
this.ngbCollapseChange.next(this._isCollapsed);
}
_runTransition(collapsed, animation) {
return ngbRunTransition(this._zone, this._element.nativeElement, ngbCollapsingTransition, {
animation,
runningTransition: 'stop',
context: { direction: collapsed ? 'hide' : 'show', dimension: this.horizontal ? 'width' : 'height' },
});
}
_runTransitionWithEvents(collapsed, animation) {
this._runTransition(collapsed, animation).subscribe(() => {
if (collapsed) {
this.hidden.emit();
}
else {
this.shown.emit();
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbCollapse, deps: [{ token: i0.ElementRef }, { token: NgbCollapseConfig }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbCollapse, isStandalone: true, selector: "[ngbCollapse]", inputs: { animation: "animation", collapsed: ["ngbCollapse", "collapsed"], horizontal: "horizontal" }, outputs: { ngbCollapseChange: "ngbCollapseChange", shown: "shown", hidden: "hidden" }, host: { properties: { "class.collapse-horizontal": "horizontal" } }, exportAs: ["ngbCollapse"], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbCollapse, decorators: [{
type: Directive,
args: [{
selector: '[ngbCollapse]',
exportAs: 'ngbCollapse',
standalone: true,
host: { '[class.collapse-horizontal]': 'horizontal' },
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: NgbCollapseConfig }, { type: i0.NgZone }]; }, propDecorators: { animation: [{
type: Input
}], collapsed: [{
type: Input,
args: ['ngbCollapse']
}], ngbCollapseChange: [{
type: Output
}], horizontal: [{
type: Input
}], shown: [{
type: Output
}], hidden: [{
type: Output
}] } });
let nextId$3 = 0;
/**
* A directive that wraps the content of an accordion item's collapsible body.
*
* The actual content is provided in a child `ng-template` element.
* Depending on the state of the accordion, the template will be either inserted or removed from the DOM.
*
* @since 14.1.0
*/
class NgbAccordionBody {
constructor() {
this._appRef = inject(ApplicationRef);
this._element = inject((ElementRef)).nativeElement;
this._item = inject(NgbAccordionItem);
this._viewRef = null;
}
ngAfterContentChecked() {
if (this._bodyTpl) {
if (this._item.animatingBodyCollapse || !this._item.destroyOnHide) {
this._createViewIfNotExists();
}
else {
this._destroyViewIfExists();
}
}
}
ngOnDestroy() {
this._destroyViewIfExists();
}
_destroyViewIfExists() {
if (this._viewRef) {
this._appRef.detachView(this._viewRef);
this._viewRef.destroy();
this._viewRef = null;
}
}
_createViewIfNotExists() {
if (!this._viewRef) {
this._viewRef = this._bodyTpl.createEmbeddedView(null);
this._viewRef.detectChanges();
this._appRef.attachView(this._viewRef);
for (const node of this._viewRef.rootNodes) {
this._element.appendChild(node);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionBody, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordionBody, isStandalone: true, selector: "[ngbAccordionBody]", host: { properties: { "class.accordion-body": "true" } }, queries: [{ propertyName: "_bodyTpl", first: true, predicate: TemplateRef, descendants: true, static: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionBody, decorators: [{
type: Directive,
args: [{
selector: '[ngbAccordionBody]',
standalone: true,
host: { '[class.accordion-body]': 'true' },
}]
}], propDecorators: { _bodyTpl: [{
type: ContentChild,
args: [TemplateRef, { static: true }]
}] } });
/**
* A directive that wraps the collapsible item's content of the accordion.
*
* Internally it reuses the [`NgbCollapse` directive](#/components/collapse)
*
* @since 14.1.0
*/
class NgbAccordionCollapse {
constructor(item, ngbCollapse) {
this.item = item;
this.ngbCollapse = ngbCollapse;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionCollapse, deps: [{ token: forwardRef(() => NgbAccordionItem) }, { token: NgbCollapse }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordionCollapse, isStandalone: true, selector: "[ngbAccordionCollapse]", host: { attributes: { "role": "region" }, properties: { "class.accordion-collapse": "true", "id": "item.collapseId", "attr.aria-labelledby": "item.toggleId" } }, exportAs: ["ngbAccordionCollapse"], hostDirectives: [{ directive: NgbCollapse }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionCollapse, decorators: [{
type: Directive,
args: [{
exportAs: 'ngbAccordionCollapse',
standalone: true,
selector: '[ngbAccordionCollapse]',
host: {
role: 'region',
'[class.accordion-collapse]': 'true',
'[id]': 'item.collapseId',
'[attr.aria-labelledby]': 'item.toggleId',
},
hostDirectives: [
{
directive: NgbCollapse,
},
],
}]
}], ctorParameters: function () { return [{ type: NgbAccordionItem, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordionItem)]
}] }, { type: NgbCollapse }]; } });
/**
* A directive to put on a toggling element inside the accordion item's header.
* It will register click handlers that toggle the associated panel and will handle accessibility attributes.
*
* This directive is used internally by the [`NgbAccordionButton` directive](#/components/accordion/api#NgbAccordionButton).
*
* @since 14.1.0
*/
class NgbAccordionToggle {
constructor(item, accordion) {
this.item = item;
this.accordion = accordion;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionToggle, deps: [{ token: forwardRef(() => NgbAccordionItem) }, { token: forwardRef(() => NgbAccordionDirective) }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordionToggle, isStandalone: true, selector: "[ngbAccordionToggle]", host: { listeners: { "click": "!item.disabled && accordion.toggle(item.id)" }, properties: { "id": "item.toggleId", "class.collapsed": "item.collapsed", "attr.aria-controls": "item.collapseId", "attr.aria-expanded": "!item.collapsed" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionToggle, decorators: [{
type: Directive,
args: [{
selector: '[ngbAccordionToggle]',
standalone: true,
host: {
'[id]': 'item.toggleId',
'[class.collapsed]': 'item.collapsed',
'[attr.aria-controls]': 'item.collapseId',
'[attr.aria-expanded]': '!item.collapsed',
'(click)': '!item.disabled && accordion.toggle(item.id)',
},
}]
}], ctorParameters: function () { return [{ type: NgbAccordionItem, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordionItem)]
}] }, { type: NgbAccordionDirective, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordionDirective)]
}] }]; } });
/**
* A directive to put on a button element inside an accordion item's header.
*
* If you want a custom markup for the header, you can also use the [`NgbAccordionToggle` directive](#/components/accordion/api#NgbAccordionToggle).
*
* @since 14.1.0
*/
class NgbAccordionButton {
constructor(item) {
this.item = item;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionButton, deps: [{ token: forwardRef(() => NgbAccordionItem) }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordionButton, isStandalone: true, selector: "button[ngbAccordionButton]", host: { attributes: { "type": "button" }, properties: { "disabled": "item.disabled", "class.accordion-button": "true" } }, hostDirectives: [{ directive: NgbAccordionToggle }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionButton, decorators: [{
type: Directive,
args: [{
selector: 'button[ngbAccordionButton]',
standalone: true,
host: {
'[disabled]': 'item.disabled',
'[class.accordion-button]': 'true',
type: 'button',
},
hostDirectives: [
{
directive: NgbAccordionToggle,
},
],
}]
}], ctorParameters: function () { return [{ type: NgbAccordionItem, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordionItem)]
}] }]; } });
/**
* A directive that wraps an accordion item's header.
*
* @since 14.1.0
*/
class NgbAccordionHeader {
constructor(item) {
this.item = item;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionHeader, deps: [{ token: forwardRef(() => NgbAccordionItem) }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.6", type: NgbAccordionHeader, isStandalone: true, selector: "[ngbAccordionHeader]", host: { attributes: { "role": "heading" }, properties: { "class.accordion-header": "true", "class.collapsed": "item.collapsed" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: NgbAccordionHeader, decorators: [{
type: Directive,
args: [{
selector: '[ngbAccordionHeader]',
standalone: true,
host: {
role: 'heading',
'[class.accordion-header]': 'true',
'[class.collapsed]': 'item.collapsed',
},
}]
}], ctorParameters: function () { return [{ type: NgbAccordionItem, decorators: [{
type: Inject,
args: [forwardRef(() => NgbAccordionItem)]
}] }]; } });
/**
* A dire