@angular-bootstrap/ngbootstrap 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Directive, EventEmitter, Output, ChangeDetectionStrategy, Component, inject, Injectable, TemplateRef, QueryList, ContentChildren, ContentChild, InjectionToken, signal, Optional, Inject, ElementRef, HostListener, HostBinding, ViewChild, ViewChildren } from '@angular/core';
2
+ import { Input, Directive, EventEmitter, Output, ChangeDetectionStrategy, Component, inject, Injectable, TemplateRef, QueryList, ContentChildren, ContentChild, InjectionToken, signal, Optional, Inject, ElementRef, HostListener, HostBinding, ViewChild, forwardRef, ViewChildren } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import * as i2 from '@angular/forms';
6
- import { FormBuilder, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
6
+ import { FormBuilder, FormControl, Validators, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
7
  import { firstValueFrom, isObservable } from 'rxjs';
8
8
 
9
9
  class NgbCellTemplate {
@@ -1755,365 +1755,568 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1755
1755
  }] } });
1756
1756
 
1757
1757
  class NgbSplitterPaneComponent {
1758
- templateRef;
1759
- size;
1760
- min;
1761
- max;
1758
+ size = ''; // e.g., '30%' or '200px'
1759
+ min = '0px';
1760
+ max = '100%';
1761
+ scrollable = true;
1762
1762
  collapsible = false;
1763
1763
  collapsed = false;
1764
1764
  collapsedChange = new EventEmitter();
1765
- toggleCollapsed(next) {
1766
- const value = next ?? !this.collapsed;
1767
- if (value !== this.collapsed) {
1768
- this.collapsed = value;
1769
- this.collapsedChange.emit(this.collapsed);
1770
- }
1771
- }
1765
+ template;
1772
1766
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1773
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterPaneComponent, isStandalone: true, selector: "ngb-splitter-pane", inputs: { size: "size", min: "min", max: "max", collapsible: "collapsible", collapsed: "collapsed" }, outputs: { collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "templateRef", first: true, predicate: ["paneTpl"], descendants: true, static: true }], ngImport: i0, template: `<ng-template #paneTpl><ng-content></ng-content></ng-template>`, isInline: true });
1767
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterPaneComponent, isStandalone: true, selector: "ngb-splitter-pane", inputs: { size: "size", min: "min", max: "max", scrollable: "scrollable", collapsible: "collapsible", collapsed: "collapsed" }, outputs: { collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "template", first: true, predicate: ["content"], descendants: true, static: true }], ngImport: i0, template: `<ng-template #content>
1768
+ <div [style.overflow]="scrollable ? 'auto' : 'hidden'" style="height: 100%; width: 100%;">
1769
+ <ng-content></ng-content>
1770
+ </div>
1771
+ </ng-template>`, isInline: true, styles: [":host{display:block;position:relative}\n"] });
1774
1772
  }
1775
1773
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, decorators: [{
1776
1774
  type: Component,
1777
- args: [{
1778
- selector: 'ngb-splitter-pane',
1779
- standalone: true,
1780
- template: `<ng-template #paneTpl><ng-content></ng-content></ng-template>`,
1781
- }]
1782
- }], propDecorators: { templateRef: [{
1783
- type: ViewChild,
1784
- args: ['paneTpl', { static: true }]
1785
- }], size: [{
1775
+ args: [{ selector: 'ngb-splitter-pane', standalone: true, template: `<ng-template #content>
1776
+ <div [style.overflow]="scrollable ? 'auto' : 'hidden'" style="height: 100%; width: 100%;">
1777
+ <ng-content></ng-content>
1778
+ </div>
1779
+ </ng-template>`, styles: [":host{display:block;position:relative}\n"] }]
1780
+ }], propDecorators: { size: [{
1786
1781
  type: Input
1787
1782
  }], min: [{
1788
1783
  type: Input
1789
1784
  }], max: [{
1790
1785
  type: Input
1786
+ }], scrollable: [{
1787
+ type: Input
1791
1788
  }], collapsible: [{
1792
1789
  type: Input
1793
1790
  }], collapsed: [{
1794
1791
  type: Input
1795
1792
  }], collapsedChange: [{
1796
1793
  type: Output
1794
+ }], template: [{
1795
+ type: ViewChild,
1796
+ args: ['content', { static: true }]
1797
1797
  }] } });
1798
1798
 
1799
1799
  class NgbSplitterComponent {
1800
+ el;
1800
1801
  panes;
1801
- container;
1802
1802
  orientation = 'horizontal';
1803
1803
  handleThickness = 12;
1804
- paneStates = [];
1805
- resizeSub;
1806
- currentDrag;
1807
- ngAfterContentInit() {
1808
- this.initStates();
1809
- this.panes.changes.subscribe(() => this.initStates());
1810
- }
1811
- ngOnChanges(changes) {
1812
- if (changes['orientation'] && !changes['orientation'].firstChange) {
1813
- this.initStates();
1814
- }
1815
- }
1816
- ngOnDestroy() {
1817
- this.detachDragListeners();
1804
+ handleIconColor = '#000';
1805
+ verticalMinHeight = '280px';
1806
+ resizable = true; // New Resize Input
1807
+ barColor = '#ccc'; // Custom Bar Color
1808
+ lineColor = '#000'; // Custom Line Color
1809
+ isDragging = false;
1810
+ currentHandleIndex = -1;
1811
+ constructor(el) {
1812
+ this.el = el;
1813
+ }
1814
+ // Listeners for global mouse movement
1815
+ onMouseMove(event) {
1816
+ if (!this.isDragging || !this.resizable)
1817
+ return;
1818
+ this.resize(event);
1818
1819
  }
1819
- onWindowResize() {
1820
- this.initStates();
1820
+ onMouseUp() {
1821
+ this.isDragging = false;
1822
+ document.body.style.cursor = 'default';
1821
1823
  }
1822
- startResize(index, event) {
1824
+ onMouseDown(event, index) {
1825
+ if (!this.resizable)
1826
+ return;
1823
1827
  event.preventDefault();
1824
- const total = this.getContainerSize();
1825
- const sizes = this.computePaneSizes(total);
1826
- this.currentDrag = {
1827
- index,
1828
- startPos: this.pointerPosition(event),
1829
- startSizes: [sizes[index], sizes[index + 1]],
1830
- };
1831
- const move = (e) => this.onDrag(e);
1832
- const up = () => this.stopDrag();
1833
- document.addEventListener('mousemove', move);
1834
- document.addEventListener('mouseup', up, { once: true });
1835
- this.resizeSub = { move, up };
1836
- }
1837
- onHandleKeydown(index, event) {
1828
+ this.isDragging = true;
1829
+ this.currentHandleIndex = index;
1830
+ document.body.style.cursor = this.orientation === 'horizontal' ? 'col-resize' : 'row-resize';
1831
+ }
1832
+ resize(event) {
1833
+ const container = this.el.nativeElement.querySelector('.splitter-container');
1834
+ const rect = container.getBoundingClientRect();
1835
+ const paneArr = this.panes.toArray();
1836
+ const paneWrappers = Array.from(this.el.nativeElement.querySelectorAll('.pane-wrapper'));
1837
+ const targetPane = paneArr[this.currentHandleIndex];
1838
+ if (!targetPane || !paneWrappers.length) {
1839
+ return;
1840
+ }
1838
1841
  const isHorizontal = this.orientation === 'horizontal';
1839
- const delta = this.handleThickness;
1840
- if ((isHorizontal && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) ||
1841
- (!isHorizontal && (event.key === 'ArrowUp' || event.key === 'ArrowDown'))) {
1842
- event.preventDefault();
1843
- const direction = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;
1844
- this.adjustPaneSizes(index, delta * direction);
1842
+ const totalPixels = isHorizontal ? rect.width : rect.height;
1843
+ if (totalPixels <= 0) {
1844
+ return;
1845
1845
  }
1846
- if (event.key === 'Enter' || event.key === ' ') {
1847
- event.preventDefault();
1848
- this.toggleFromHandle(index);
1846
+ const mousePos = isHorizontal ? event.clientX - rect.left : event.clientY - rect.top;
1847
+ let consumed = 0;
1848
+ for (let i = 0; i < this.currentHandleIndex; i++) {
1849
+ const prevPaneElement = paneWrappers[i];
1850
+ if (!prevPaneElement) {
1851
+ continue;
1852
+ }
1853
+ const prevRect = prevPaneElement.getBoundingClientRect();
1854
+ consumed += isHorizontal ? prevRect.width : prevRect.height;
1855
+ consumed += this.handleThickness;
1849
1856
  }
1850
- }
1851
- toggleFromHandle(index) {
1852
- const panesArray = this.panes.toArray();
1853
- const target = panesArray[index].collapsible ? panesArray[index] : panesArray[index + 1];
1854
- if (target?.collapsible) {
1855
- target.toggleCollapsed();
1856
- this.initStates();
1857
+ let newSizeValue = ((mousePos - consumed) / totalPixels) * 100;
1858
+ const minPercent = this.sizeToPercent(targetPane.min, totalPixels);
1859
+ const maxPercent = this.sizeToPercent(targetPane.max, totalPixels);
1860
+ if (minPercent !== null) {
1861
+ newSizeValue = Math.max(newSizeValue, minPercent);
1857
1862
  }
1858
- }
1859
- onDrag(event) {
1860
- if (!this.currentDrag) {
1861
- return;
1863
+ if (maxPercent !== null) {
1864
+ newSizeValue = Math.min(newSizeValue, maxPercent);
1862
1865
  }
1866
+ newSizeValue = Math.max(0, Math.min(100, newSizeValue));
1867
+ targetPane.size = `${newSizeValue}%`;
1868
+ }
1869
+ onHandleToggle(event, index) {
1870
+ event.stopPropagation();
1863
1871
  event.preventDefault();
1864
- const delta = this.pointerPosition(event) - this.currentDrag.startPos;
1865
- this.adjustPaneSizes(this.currentDrag.index, delta, this.currentDrag.startSizes);
1866
- }
1867
- stopDrag() {
1868
- this.detachDragListeners();
1869
- this.currentDrag = undefined;
1870
- }
1871
- adjustPaneSizes(index, delta, startSizes) {
1872
- const total = this.getContainerSize();
1873
- const sizes = startSizes ?? this.computePaneSizes(total);
1874
- const panesArray = this.panes.toArray();
1875
- const first = panesArray[index];
1876
- const second = panesArray[index + 1];
1877
- if (first.collapsed || second.collapsed) {
1878
- return;
1879
- }
1880
- let firstSize = sizes[0] + delta;
1881
- let secondSize = sizes[1] - delta;
1882
- const firstLimits = this.getLimits(first, total);
1883
- const secondLimits = this.getLimits(second, total);
1884
- firstSize = this.clamp(firstSize, firstLimits.minPx, firstLimits.maxPx);
1885
- secondSize = this.clamp(secondSize, secondLimits.minPx, secondLimits.maxPx);
1886
- if (firstSize + secondSize > 0) {
1887
- this.paneStates[index].sizePx = firstSize;
1888
- this.paneStates[index + 1].sizePx = secondSize;
1889
- }
1890
- }
1891
- initStates() {
1892
- const total = this.getContainerSize();
1893
- const sizes = this.computePaneSizes(total);
1894
- const panesArray = this.panes.toArray();
1895
- this.paneStates = panesArray.map((pane, idx) => {
1896
- const limits = this.getLimits(pane, total);
1897
- return {
1898
- id: `ngb-splitter-pane-${idx}`,
1899
- sizePx: pane.collapsed ? 0 : sizes[idx],
1900
- minPx: limits.minPx,
1901
- maxPx: limits.maxPx,
1902
- };
1903
- });
1872
+ this.togglePane(index);
1904
1873
  }
1905
- computePaneSizes(total) {
1906
- const panesArray = this.panes?.toArray() ?? [];
1907
- if (!panesArray.length)
1908
- return [];
1909
- const resolved = panesArray.map((pane) => pane.collapsed ? 0 : this.resolveSize(pane.size, total));
1910
- const specifiedTotal = resolved.reduce((sum, val) => sum + (val ?? 0), 0);
1911
- const unspecified = resolved.filter((v) => v == null).length;
1912
- const remaining = Math.max(total - specifiedTotal, 0);
1913
- const autoSize = unspecified > 0 ? remaining / unspecified : 0;
1914
- return resolved.map((val) => {
1915
- if (val != null) {
1916
- return val;
1917
- }
1918
- if (autoSize) {
1919
- return autoSize;
1920
- }
1921
- return total / panesArray.length;
1922
- });
1874
+ getCollapseIcon(pane) {
1875
+ if (!pane.collapsible) {
1876
+ return '';
1877
+ }
1878
+ if (this.orientation === 'horizontal') {
1879
+ return pane.collapsed ? 'bi-caret-right-fill' : 'bi-caret-left-fill';
1880
+ }
1881
+ return pane.collapsed ? 'bi-caret-down-fill' : 'bi-caret-up-fill';
1923
1882
  }
1924
- resolveSize(value, total) {
1925
- if (!value)
1926
- return undefined;
1883
+ getPaneFlex(pane) {
1884
+ if (pane.collapsed) {
1885
+ return '0 0 0px';
1886
+ }
1887
+ if (pane.size) {
1888
+ return `0 0 ${pane.size}`;
1889
+ }
1890
+ return '1 1 0px';
1891
+ }
1892
+ sizeToPercent(value, totalPixels) {
1893
+ if (!value) {
1894
+ return null;
1895
+ }
1927
1896
  const trimmed = value.trim();
1897
+ if (!trimmed) {
1898
+ return null;
1899
+ }
1928
1900
  if (trimmed.endsWith('%')) {
1929
- const pct = Number.parseFloat(trimmed.slice(0, -1));
1930
- return (pct / 100) * total;
1901
+ const percent = parseFloat(trimmed);
1902
+ return Number.isFinite(percent) ? percent : null;
1903
+ }
1904
+ const numeric = parseFloat(trimmed);
1905
+ if (!Number.isFinite(numeric)) {
1906
+ return null;
1931
1907
  }
1932
1908
  if (trimmed.endsWith('px')) {
1933
- return Number.parseFloat(trimmed.replace('px', ''));
1909
+ return totalPixels > 0 ? (numeric / totalPixels) * 100 : null;
1910
+ }
1911
+ return numeric;
1912
+ }
1913
+ togglePane(index) {
1914
+ const pane = this.panes.toArray()[index];
1915
+ pane.collapsed = !pane.collapsed;
1916
+ pane.collapsedChange.emit(pane.collapsed);
1917
+ }
1918
+ getSeparatorAriaOrientation() {
1919
+ // For left/right panes, the separator is vertical. For top/bottom panes, it is horizontal.
1920
+ return this.orientation === 'horizontal' ? 'vertical' : 'horizontal';
1921
+ }
1922
+ getHandleValueNow(index) {
1923
+ const container = this.el.nativeElement.querySelector('.splitter-container');
1924
+ const rect = container.getBoundingClientRect();
1925
+ const paneWrappers = Array.from(this.el.nativeElement.querySelectorAll('.pane-wrapper'));
1926
+ const target = paneWrappers[index];
1927
+ const totalPixels = this.orientation === 'horizontal' ? rect.width : rect.height;
1928
+ if (!target || totalPixels <= 0) {
1929
+ return null;
1934
1930
  }
1935
- const asNumber = Number.parseFloat(trimmed);
1936
- return Number.isFinite(asNumber) ? asNumber : undefined;
1931
+ const paneRect = target.getBoundingClientRect();
1932
+ const panePixels = this.orientation === 'horizontal' ? paneRect.width : paneRect.height;
1933
+ return Math.round((panePixels / totalPixels) * 100);
1937
1934
  }
1938
- getLimits(pane, total) {
1939
- return {
1940
- minPx: pane.collapsed ? 0 : this.resolveSize(pane.min, total),
1941
- maxPx: pane.collapsed ? 0 : this.resolveSize(pane.max, total),
1942
- };
1935
+ getHandleValueMin(index) {
1936
+ const pane = this.panes?.toArray?.()[index];
1937
+ const container = this.el.nativeElement.querySelector('.splitter-container');
1938
+ const rect = container.getBoundingClientRect();
1939
+ const totalPixels = this.orientation === 'horizontal' ? rect.width : rect.height;
1940
+ if (!pane || totalPixels <= 0) {
1941
+ return null;
1942
+ }
1943
+ return this.sizeToPercent(pane.min, totalPixels);
1944
+ }
1945
+ getHandleValueMax(index) {
1946
+ const pane = this.panes?.toArray?.()[index];
1947
+ const container = this.el.nativeElement.querySelector('.splitter-container');
1948
+ const rect = container.getBoundingClientRect();
1949
+ const totalPixels = this.orientation === 'horizontal' ? rect.width : rect.height;
1950
+ if (!pane || totalPixels <= 0) {
1951
+ return null;
1952
+ }
1953
+ return this.sizeToPercent(pane.max, totalPixels);
1943
1954
  }
1944
- clamp(value, min, max) {
1945
- let next = value;
1946
- if (min != null)
1947
- next = Math.max(min, next);
1948
- if (max != null)
1949
- next = Math.min(max, next);
1950
- return next;
1951
- }
1952
- pointerPosition(event) {
1953
- return this.orientation === 'horizontal' ? event.clientX : event.clientY;
1954
- }
1955
- getContainerSize() {
1956
- const rect = this.container?.nativeElement.getBoundingClientRect();
1957
- return rect && this.orientation === 'horizontal' ? rect.width : rect?.height || 0;
1958
- }
1959
- detachDragListeners() {
1960
- if (this.resizeSub) {
1961
- document.removeEventListener('mousemove', this.resizeSub.move);
1962
- document.removeEventListener('mouseup', this.resizeSub.up);
1963
- this.resizeSub = undefined;
1964
- }
1965
- }
1966
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1967
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterComponent, isStandalone: true, selector: "ngb-splitter", inputs: { orientation: "orientation", handleThickness: "handleThickness" }, host: { listeners: { "window:resize": "onWindowResize()" } }, queries: [{ propertyName: "panes", predicate: NgbSplitterPaneComponent }], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
1968
- <div
1969
- #container
1970
- class="d-flex"
1971
- [class.flex-row]="orientation === 'horizontal'"
1972
- [class.flex-column]="orientation === 'vertical'"
1973
- role="group"
1974
- >
1975
- <ng-container *ngFor="let pane of panes.toArray(); let i = index">
1976
- <div
1977
- class="d-flex flex-column overflow-auto"
1978
- [attr.data-pane-id]="paneStates[i]?.id"
1979
- [class.d-none]="pane.collapsed"
1980
- [style.flex-basis.px]="paneStates[i]?.sizePx"
1981
- [style.min-width.px]="orientation === 'horizontal' ? paneStates[i]?.minPx : undefined"
1982
- [style.max-width.px]="orientation === 'horizontal' ? paneStates[i]?.maxPx : undefined"
1983
- [style.min-height.px]="orientation === 'vertical' ? paneStates[i]?.minPx : undefined"
1984
- [style.max-height.px]="orientation === 'vertical' ? paneStates[i]?.maxPx : undefined"
1955
+ onHandleKeyDown(event, index) {
1956
+ if (event.key === 'Enter') {
1957
+ const pane = this.panes.toArray()[index];
1958
+ if (pane?.collapsible) {
1959
+ event.preventDefault();
1960
+ this.togglePane(index);
1961
+ }
1962
+ return;
1963
+ }
1964
+ const isHorizontal = this.orientation === 'horizontal';
1965
+ const negativeKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
1966
+ const positiveKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
1967
+ if (event.key !== negativeKey && event.key !== positiveKey) {
1968
+ return;
1969
+ }
1970
+ if (!this.resizable) {
1971
+ return;
1972
+ }
1973
+ event.preventDefault();
1974
+ const delta = (event.shiftKey ? 5 : 2) * (event.key === positiveKey ? 1 : -1);
1975
+ this.resizeByPercent(index, delta);
1976
+ }
1977
+ resizeByPercent(index, deltaPercent) {
1978
+ const container = this.el.nativeElement.querySelector('.splitter-container');
1979
+ const rect = container.getBoundingClientRect();
1980
+ const paneArr = this.panes.toArray();
1981
+ const paneWrappers = Array.from(this.el.nativeElement.querySelectorAll('.pane-wrapper'));
1982
+ const targetPane = paneArr[index];
1983
+ const targetWrapper = paneWrappers[index];
1984
+ if (!targetPane || !targetWrapper) {
1985
+ return;
1986
+ }
1987
+ const totalPixels = this.orientation === 'horizontal' ? rect.width : rect.height;
1988
+ if (totalPixels <= 0) {
1989
+ return;
1990
+ }
1991
+ const wrapperRect = targetWrapper.getBoundingClientRect();
1992
+ const currentPixels = this.orientation === 'horizontal' ? wrapperRect.width : wrapperRect.height;
1993
+ let nextPercent = (currentPixels / totalPixels) * 100 + deltaPercent;
1994
+ const minPercent = this.sizeToPercent(targetPane.min, totalPixels);
1995
+ const maxPercent = this.sizeToPercent(targetPane.max, totalPixels);
1996
+ if (minPercent !== null) {
1997
+ nextPercent = Math.max(nextPercent, minPercent);
1998
+ }
1999
+ if (maxPercent !== null) {
2000
+ nextPercent = Math.min(nextPercent, maxPercent);
2001
+ }
2002
+ nextPercent = Math.max(0, Math.min(100, nextPercent));
2003
+ targetPane.size = `${nextPercent}%`;
2004
+ }
2005
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2006
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterComponent, isStandalone: true, selector: "ngb-splitter", inputs: { orientation: "orientation", handleThickness: "handleThickness", handleIconColor: "handleIconColor", verticalMinHeight: "verticalMinHeight", resizable: "resizable", barColor: "barColor", lineColor: "lineColor" }, host: { listeners: { "window:mousemove": "onMouseMove($event)", "window:mouseup": "onMouseUp()" } }, queries: [{ propertyName: "panes", predicate: NgbSplitterPaneComponent }], ngImport: i0, template: `
2007
+ <div class="splitter-container" [class.vertical]="orientation === 'vertical'"
2008
+ [style.min-height]="orientation === 'vertical' ? verticalMinHeight : null"
2009
+ [style.height]="orientation === 'vertical' ? verticalMinHeight : null">
2010
+ <!-- Iterate through the QueryList of panes -->
2011
+ <ng-container *ngFor="let pane of panes; let i = index">
2012
+ <div class="pane-wrapper" [style.flex]="getPaneFlex(pane)"
2013
+ [style.min-width]="orientation === 'horizontal' ? (pane.min || '0px') : null"
2014
+ [style.max-width]="orientation === 'horizontal' ? pane.max : null"
2015
+ [style.min-height]="orientation === 'vertical' ? (pane.min || '0px') : null"
2016
+ [style.max-height]="orientation === 'vertical' ? pane.max : null"
1985
2017
  >
1986
- <ng-container *ngTemplateOutlet="pane.templateRef"></ng-container>
2018
+ <!-- Access the 'template' property we defined in the Pane component -->
2019
+ <ng-container [ngTemplateOutlet]="pane.template"></ng-container>
1987
2020
  </div>
1988
2021
 
1989
- <button
1990
- *ngIf="i < panes.length - 1"
1991
- type="button"
1992
- class="bg-transparent border-0 p-0 d-flex align-items-center justify-content-center"
1993
- [style.width.px]="orientation === 'horizontal' ? handleThickness : undefined"
1994
- [style.height.px]="orientation === 'vertical' ? handleThickness : undefined"
1995
- [class.w-100]="orientation === 'vertical'"
1996
- [class.h-100]="orientation === 'horizontal'"
1997
- role="separator"
1998
- [attr.aria-orientation]="orientation"
1999
- [attr.aria-controls]="paneStates[i]?.id + ' ' + paneStates[i + 1]?.id"
2000
- tabindex="0"
2001
- (mousedown)="startResize(i, $event)"
2002
- (keydown)="onHandleKeydown(i, $event)"
2003
- (dblclick)="toggleFromHandle(i)"
2004
- aria-label="Splitter resize handle"
2005
- >
2006
- <span class="border bg-light" [style.width.px]="orientation === 'horizontal' ? 2 : handleThickness" [style.height.px]="orientation === 'vertical' ? 2 : handleThickness"></span>
2007
- </button>
2022
+ <div *ngIf="i < panes.length - 1" class="splitbar"
2023
+ [class.resizable]="resizable"
2024
+ [style.background-color]="barColor"
2025
+ [style.width.px]="orientation === 'horizontal' ? handleThickness : '100%'"
2026
+ [style.height.px]="orientation === 'vertical' ? handleThickness : '100%'"
2027
+ role="separator"
2028
+ tabindex="0"
2029
+ [attr.aria-orientation]="getSeparatorAriaOrientation()"
2030
+ [attr.aria-valuenow]="getHandleValueNow(i)"
2031
+ [attr.aria-valuemin]="getHandleValueMin(i)"
2032
+ [attr.aria-valuemax]="getHandleValueMax(i)"
2033
+ (keydown)="onHandleKeyDown($event, i)"
2034
+ (mousedown)="onMouseDown($event, i)">
2035
+ <div class="handle-line" [style.background-color]="lineColor">
2036
+ <span
2037
+ *ngIf="pane.collapsible"
2038
+ class="collapse-arrow bi"
2039
+ [ngClass]="getCollapseIcon(pane)"
2040
+ [style.color]="handleIconColor"
2041
+ (click)="onHandleToggle($event, i)"
2042
+ aria-hidden="true"
2043
+ ></span>
2044
+ </div>
2045
+ </div>
2008
2046
  </ng-container>
2009
2047
  </div>
2010
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2048
+ `, isInline: true, styles: [":host{display:block;width:100%}.splitter-container{display:flex;height:100%;width:100%}.splitter-container.vertical{flex-direction:column}.splitbar{background:#ccc;width:5px;position:relative;align-items:center;justify-content:center;flex:0 0 auto;z-index:10}.splitbar.resizable{cursor:col-resize}.splitbar:hover{filter:brightness(90%)}.handle-line{border-radius:2px;opacity:.8;left:45%;position:absolute;top:45%;display:flex;align-items:center;justify-content:center}.splitter-container:not(.vertical) .handle-line{width:2px;height:10px}.pane-wrapper{position:relative;min-width:0;min-height:0;overflow:hidden;flex-shrink:1}.vertical .handle-line{height:2px;width:10px}.vertical .splitbar.resizable{cursor:row-resize}.splitter-container.vertical .splitter-container .splitbar.resizable{cursor:col-resize}.vertical .splitbar{width:100%}.collapse-btn{cursor:pointer;background:#666;width:10px;height:10px;position:absolute;top:50%}.collapse-arrow{font-size:.95rem;-webkit-user-select:none;user-select:none;cursor:pointer;line-height:1;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;position:absolute;top:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2011
2049
  }
2012
2050
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, decorators: [{
2013
2051
  type: Component,
2014
- args: [{
2015
- selector: 'ngb-splitter',
2016
- standalone: true,
2017
- imports: [CommonModule, NgbSplitterPaneComponent],
2018
- changeDetection: ChangeDetectionStrategy.OnPush,
2019
- template: `
2020
- <div
2021
- #container
2022
- class="d-flex"
2023
- [class.flex-row]="orientation === 'horizontal'"
2024
- [class.flex-column]="orientation === 'vertical'"
2025
- role="group"
2026
- >
2027
- <ng-container *ngFor="let pane of panes.toArray(); let i = index">
2028
- <div
2029
- class="d-flex flex-column overflow-auto"
2030
- [attr.data-pane-id]="paneStates[i]?.id"
2031
- [class.d-none]="pane.collapsed"
2032
- [style.flex-basis.px]="paneStates[i]?.sizePx"
2033
- [style.min-width.px]="orientation === 'horizontal' ? paneStates[i]?.minPx : undefined"
2034
- [style.max-width.px]="orientation === 'horizontal' ? paneStates[i]?.maxPx : undefined"
2035
- [style.min-height.px]="orientation === 'vertical' ? paneStates[i]?.minPx : undefined"
2036
- [style.max-height.px]="orientation === 'vertical' ? paneStates[i]?.maxPx : undefined"
2052
+ args: [{ selector: 'ngb-splitter', standalone: true, imports: [CommonModule, NgbSplitterPaneComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2053
+ <div class="splitter-container" [class.vertical]="orientation === 'vertical'"
2054
+ [style.min-height]="orientation === 'vertical' ? verticalMinHeight : null"
2055
+ [style.height]="orientation === 'vertical' ? verticalMinHeight : null">
2056
+ <!-- Iterate through the QueryList of panes -->
2057
+ <ng-container *ngFor="let pane of panes; let i = index">
2058
+ <div class="pane-wrapper" [style.flex]="getPaneFlex(pane)"
2059
+ [style.min-width]="orientation === 'horizontal' ? (pane.min || '0px') : null"
2060
+ [style.max-width]="orientation === 'horizontal' ? pane.max : null"
2061
+ [style.min-height]="orientation === 'vertical' ? (pane.min || '0px') : null"
2062
+ [style.max-height]="orientation === 'vertical' ? pane.max : null"
2037
2063
  >
2038
- <ng-container *ngTemplateOutlet="pane.templateRef"></ng-container>
2064
+ <!-- Access the 'template' property we defined in the Pane component -->
2065
+ <ng-container [ngTemplateOutlet]="pane.template"></ng-container>
2039
2066
  </div>
2040
2067
 
2041
- <button
2042
- *ngIf="i < panes.length - 1"
2043
- type="button"
2044
- class="bg-transparent border-0 p-0 d-flex align-items-center justify-content-center"
2045
- [style.width.px]="orientation === 'horizontal' ? handleThickness : undefined"
2046
- [style.height.px]="orientation === 'vertical' ? handleThickness : undefined"
2047
- [class.w-100]="orientation === 'vertical'"
2048
- [class.h-100]="orientation === 'horizontal'"
2049
- role="separator"
2050
- [attr.aria-orientation]="orientation"
2051
- [attr.aria-controls]="paneStates[i]?.id + ' ' + paneStates[i + 1]?.id"
2052
- tabindex="0"
2053
- (mousedown)="startResize(i, $event)"
2054
- (keydown)="onHandleKeydown(i, $event)"
2055
- (dblclick)="toggleFromHandle(i)"
2056
- aria-label="Splitter resize handle"
2057
- >
2058
- <span class="border bg-light" [style.width.px]="orientation === 'horizontal' ? 2 : handleThickness" [style.height.px]="orientation === 'vertical' ? 2 : handleThickness"></span>
2059
- </button>
2068
+ <div *ngIf="i < panes.length - 1" class="splitbar"
2069
+ [class.resizable]="resizable"
2070
+ [style.background-color]="barColor"
2071
+ [style.width.px]="orientation === 'horizontal' ? handleThickness : '100%'"
2072
+ [style.height.px]="orientation === 'vertical' ? handleThickness : '100%'"
2073
+ role="separator"
2074
+ tabindex="0"
2075
+ [attr.aria-orientation]="getSeparatorAriaOrientation()"
2076
+ [attr.aria-valuenow]="getHandleValueNow(i)"
2077
+ [attr.aria-valuemin]="getHandleValueMin(i)"
2078
+ [attr.aria-valuemax]="getHandleValueMax(i)"
2079
+ (keydown)="onHandleKeyDown($event, i)"
2080
+ (mousedown)="onMouseDown($event, i)">
2081
+ <div class="handle-line" [style.background-color]="lineColor">
2082
+ <span
2083
+ *ngIf="pane.collapsible"
2084
+ class="collapse-arrow bi"
2085
+ [ngClass]="getCollapseIcon(pane)"
2086
+ [style.color]="handleIconColor"
2087
+ (click)="onHandleToggle($event, i)"
2088
+ aria-hidden="true"
2089
+ ></span>
2090
+ </div>
2091
+ </div>
2060
2092
  </ng-container>
2061
2093
  </div>
2062
- `,
2063
- }]
2064
- }], propDecorators: { panes: [{
2094
+ `, styles: [":host{display:block;width:100%}.splitter-container{display:flex;height:100%;width:100%}.splitter-container.vertical{flex-direction:column}.splitbar{background:#ccc;width:5px;position:relative;align-items:center;justify-content:center;flex:0 0 auto;z-index:10}.splitbar.resizable{cursor:col-resize}.splitbar:hover{filter:brightness(90%)}.handle-line{border-radius:2px;opacity:.8;left:45%;position:absolute;top:45%;display:flex;align-items:center;justify-content:center}.splitter-container:not(.vertical) .handle-line{width:2px;height:10px}.pane-wrapper{position:relative;min-width:0;min-height:0;overflow:hidden;flex-shrink:1}.vertical .handle-line{height:2px;width:10px}.vertical .splitbar.resizable{cursor:row-resize}.splitter-container.vertical .splitter-container .splitbar.resizable{cursor:col-resize}.vertical .splitbar{width:100%}.collapse-btn{cursor:pointer;background:#666;width:10px;height:10px;position:absolute;top:50%}.collapse-arrow{font-size:.95rem;-webkit-user-select:none;user-select:none;cursor:pointer;line-height:1;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;position:absolute;top:100%}\n"] }]
2095
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { panes: [{
2065
2096
  type: ContentChildren,
2066
2097
  args: [NgbSplitterPaneComponent]
2067
- }], container: [{
2068
- type: ViewChild,
2069
- args: ['container', { static: true }]
2070
2098
  }], orientation: [{
2071
2099
  type: Input
2072
2100
  }], handleThickness: [{
2073
2101
  type: Input
2074
- }], onWindowResize: [{
2102
+ }], handleIconColor: [{
2103
+ type: Input
2104
+ }], verticalMinHeight: [{
2105
+ type: Input
2106
+ }], resizable: [{
2107
+ type: Input
2108
+ }], barColor: [{
2109
+ type: Input
2110
+ }], lineColor: [{
2111
+ type: Input
2112
+ }], onMouseMove: [{
2075
2113
  type: HostListener,
2076
- args: ['window:resize']
2114
+ args: ['window:mousemove', ['$event']]
2115
+ }], onMouseUp: [{
2116
+ type: HostListener,
2117
+ args: ['window:mouseup']
2118
+ }] } });
2119
+
2120
+ class NgbChipsComponent {
2121
+ items = [];
2122
+ removable = true;
2123
+ ariaLabel;
2124
+ removeLabel;
2125
+ remove = new EventEmitter();
2126
+ trackById = (_, item) => item.id;
2127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbChipsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2128
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbChipsComponent, isStandalone: true, selector: "ngb-chips", inputs: { items: "items", removable: "removable", ariaLabel: "ariaLabel", removeLabel: "removeLabel" }, outputs: { remove: "remove" }, ngImport: i0, template: `
2129
+ <div class="ngb-chips" [attr.aria-label]="ariaLabel">
2130
+ <span
2131
+ *ngFor="let item of items; trackBy: trackById"
2132
+ class="badge rounded-pill bg-secondary ngb-chip"
2133
+ [class.opacity-50]="!!item.disabled"
2134
+ >
2135
+ <span class="ngb-chip-label">{{ item.label }}</span>
2136
+ <button
2137
+ *ngIf="removable"
2138
+ type="button"
2139
+ class="ngb-chip-remove"
2140
+ [disabled]="!!item.disabled"
2141
+ (click)="remove.emit(item)"
2142
+ [attr.aria-label]="removeLabel || 'Remove'"
2143
+ >
2144
+ &times;
2145
+ </button>
2146
+ </span>
2147
+ </div>
2148
+ `, isInline: true, styles: [":host{display:block}.ngb-chips{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}.ngb-chip{display:inline-flex;align-items:center;gap:.25rem;max-width:100%}.ngb-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngb-chip-remove{border:0;background:transparent;padding:0;line-height:1;cursor:pointer;font-size:1rem;opacity:.85}.ngb-chip-remove:disabled{cursor:default;opacity:.4}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2149
+ }
2150
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbChipsComponent, decorators: [{
2151
+ type: Component,
2152
+ args: [{ selector: 'ngb-chips', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2153
+ <div class="ngb-chips" [attr.aria-label]="ariaLabel">
2154
+ <span
2155
+ *ngFor="let item of items; trackBy: trackById"
2156
+ class="badge rounded-pill bg-secondary ngb-chip"
2157
+ [class.opacity-50]="!!item.disabled"
2158
+ >
2159
+ <span class="ngb-chip-label">{{ item.label }}</span>
2160
+ <button
2161
+ *ngIf="removable"
2162
+ type="button"
2163
+ class="ngb-chip-remove"
2164
+ [disabled]="!!item.disabled"
2165
+ (click)="remove.emit(item)"
2166
+ [attr.aria-label]="removeLabel || 'Remove'"
2167
+ >
2168
+ &times;
2169
+ </button>
2170
+ </span>
2171
+ </div>
2172
+ `, styles: [":host{display:block}.ngb-chips{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}.ngb-chip{display:inline-flex;align-items:center;gap:.25rem;max-width:100%}.ngb-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngb-chip-remove{border:0;background:transparent;padding:0;line-height:1;cursor:pointer;font-size:1rem;opacity:.85}.ngb-chip-remove:disabled{cursor:default;opacity:.4}\n"] }]
2173
+ }], propDecorators: { items: [{
2174
+ type: Input
2175
+ }], removable: [{
2176
+ type: Input
2177
+ }], ariaLabel: [{
2178
+ type: Input
2179
+ }], removeLabel: [{
2180
+ type: Input
2181
+ }], remove: [{
2182
+ type: Output
2077
2183
  }] } });
2078
2184
 
2079
2185
  class NgbTypeaheadComponent {
2186
+ host;
2187
+ cdr;
2080
2188
  data = [];
2081
2189
  debounceTime = 200;
2082
2190
  characterTyped = 1;
2083
2191
  limit = 10;
2084
2192
  selectExact = false;
2085
2193
  multiSelect = false;
2086
- i18n;
2194
+ matchSelection = false;
2195
+ showDropdownButton = false;
2196
+ showClearButton = false;
2197
+ updateOnBlur = false;
2198
+ updateOnTab = true;
2199
+ separator = ',';
2200
+ chips = false;
2201
+ // `TemplateRef` types can become non-assignable in monorepo setups with multiple Angular installations.
2202
+ // Using `any` keeps the API flexible while still supporting Angular templates at runtime.
2087
2203
  itemTemplate;
2204
+ i18n;
2205
+ projectedItemTemplate;
2206
+ completeMethod = new EventEmitter();
2207
+ onSelect = new EventEmitter();
2208
+ onUnselect = new EventEmitter();
2209
+ onAdd = new EventEmitter();
2210
+ onFocus = new EventEmitter();
2211
+ onBlur = new EventEmitter();
2212
+ onDropdownClick = new EventEmitter();
2213
+ onClear = new EventEmitter();
2214
+ onInputKeydown = new EventEmitter();
2215
+ onKeyUp = new EventEmitter();
2216
+ onShow = new EventEmitter();
2217
+ onHide = new EventEmitter();
2218
+ onLazyLoad = new EventEmitter();
2088
2219
  selectedItems = new EventEmitter();
2089
2220
  selectionChange = new EventEmitter();
2090
2221
  onChange = new EventEmitter();
2091
- onScrollEvent = new EventEmitter();
2222
+ onScrollEvent = new EventEmitter(); // legacy, kept for compatibility
2092
2223
  scroller;
2093
2224
  inputEl;
2094
2225
  itemButtons;
2226
+ disabled = false;
2095
2227
  query = '';
2096
2228
  filtered = [];
2097
2229
  visible = [];
2098
2230
  selected = [];
2231
+ overlayVisible = false;
2232
+ openedByDropdown = false;
2233
+ hasSearched = false;
2234
+ activeIndex = -1;
2235
+ inputFocused = false;
2236
+ pointerDownInside = false;
2237
+ lastFilterTerm = '';
2238
+ keepOpenOnEmpty = false;
2239
+ overlayId = `ngb-typeahead-overlay-${Math.random().toString(36).slice(2)}`;
2099
2240
  viewportHeight = 240;
2100
2241
  itemHeight = 40;
2101
2242
  beforePadding = 0;
2102
2243
  afterPadding = 0;
2103
2244
  debounceId;
2245
+ onControlChange = () => { };
2246
+ onControlTouched = () => { };
2247
+ controlValue = null;
2104
2248
  ngAfterViewInit() {
2249
+ // do not open overlay on init; just ensure viewport state is consistent
2105
2250
  this.applyFilter('');
2106
2251
  }
2107
2252
  ngOnChanges(changes) {
2108
2253
  if (changes['data'] && !changes['data'].firstChange) {
2109
2254
  this.applyFilter(this.query);
2110
2255
  }
2256
+ if (changes['data'] && this.controlValue != null && !this.inputFocused && !this.overlayVisible) {
2257
+ this.applyControlValue(this.controlValue);
2258
+ }
2111
2259
  }
2112
2260
  ngOnDestroy() {
2113
2261
  if (this.debounceId) {
2114
2262
  clearTimeout(this.debounceId);
2115
2263
  }
2116
2264
  }
2265
+ constructor(host, cdr) {
2266
+ this.host = host;
2267
+ this.cdr = cdr;
2268
+ }
2269
+ onDocumentMouseDown(event) {
2270
+ const target = event.target;
2271
+ if (!target)
2272
+ return;
2273
+ const inside = this.host.nativeElement.contains(target);
2274
+ this.pointerDownInside = inside;
2275
+ if (inside)
2276
+ return;
2277
+ if (!this.overlayVisible)
2278
+ return;
2279
+ this.commitInputOnClose();
2280
+ this.hideOverlay(event);
2281
+ }
2282
+ onDocumentFocusIn(event) {
2283
+ if (!this.overlayVisible)
2284
+ return;
2285
+ const target = event.target;
2286
+ if (!target)
2287
+ return;
2288
+ if (this.host.nativeElement.contains(target))
2289
+ return;
2290
+ this.commitInputOnClose();
2291
+ this.hideOverlay(event);
2292
+ }
2293
+ get activeDescendantId() {
2294
+ if (!this.overlayVisible)
2295
+ return null;
2296
+ if (this.activeIndex < 0 || this.activeIndex >= this.visible.length)
2297
+ return null;
2298
+ return this.getOptionId(this.activeIndex);
2299
+ }
2300
+ get showNoResults() {
2301
+ return this.overlayVisible && this.hasSearched && this.filtered.length === 0;
2302
+ }
2303
+ get displayQuery() {
2304
+ if (!this.multiSelect) {
2305
+ return this.query;
2306
+ }
2307
+ if (this.chips) {
2308
+ return this.query;
2309
+ }
2310
+ const term = (this.query || '').trim();
2311
+ const prefix = this.selected.map((s) => s.label).join(', ');
2312
+ if (!prefix) {
2313
+ return this.query;
2314
+ }
2315
+ if (term) {
2316
+ return `${prefix}, ${term}`;
2317
+ }
2318
+ return prefix;
2319
+ }
2117
2320
  get selectionSummary() {
2118
2321
  if (!this.selected.length)
2119
2322
  return '';
@@ -2123,28 +2326,179 @@ class NgbTypeaheadComponent {
2123
2326
  const firstTwo = this.selected.slice(0, 2).map((s) => s.label).join(', ');
2124
2327
  return `${firstTwo} (+${this.selected.length - 2})`;
2125
2328
  }
2329
+ get resolvedItemTemplate() {
2330
+ return this.itemTemplate ?? this.projectedItemTemplate;
2331
+ }
2332
+ writeValue(value) {
2333
+ this.controlValue = value;
2334
+ this.applyControlValue(value);
2335
+ }
2336
+ applyControlValue(value) {
2337
+ if (this.multiSelect) {
2338
+ const asArray = Array.isArray(value) ? value : value == null ? [] : [value];
2339
+ const next = asArray.map((v) => this.coerceToItem(v)).filter(Boolean);
2340
+ const seen = new Set();
2341
+ this.selected = next.filter((i) => (seen.has(i.id) ? false : (seen.add(i.id), true)));
2342
+ this.query = '';
2343
+ this.keepOpenOnEmpty = false;
2344
+ this.applyFilter('');
2345
+ this.cdr.markForCheck();
2346
+ return;
2347
+ }
2348
+ if (value == null) {
2349
+ this.selected = [];
2350
+ this.query = '';
2351
+ this.keepOpenOnEmpty = false;
2352
+ this.applyFilter('');
2353
+ this.cdr.markForCheck();
2354
+ return;
2355
+ }
2356
+ const item = this.coerceToItem(value);
2357
+ if (!item)
2358
+ return;
2359
+ this.selected = [item];
2360
+ this.query = item.label;
2361
+ if (this.inputEl?.nativeElement) {
2362
+ this.inputEl.nativeElement.value = item.label;
2363
+ }
2364
+ this.keepOpenOnEmpty = false;
2365
+ this.applyFilter(this.query);
2366
+ this.cdr.markForCheck();
2367
+ }
2368
+ registerOnChange(fn) {
2369
+ this.onControlChange = fn;
2370
+ }
2371
+ registerOnTouched(fn) {
2372
+ this.onControlTouched = fn;
2373
+ }
2374
+ setDisabledState(isDisabled) {
2375
+ this.disabled = isDisabled;
2376
+ if (isDisabled) {
2377
+ this.hideOverlay();
2378
+ }
2379
+ this.cdr.markForCheck();
2380
+ }
2126
2381
  onInput(value) {
2127
- this.query = value;
2382
+ if (this.disabled)
2383
+ return;
2384
+ this.keepOpenOnEmpty = false;
2385
+ const trimmed = (value ?? '').trim();
2386
+ if (!this.multiSelect && this.selected.length) {
2387
+ const selectedLabel = (this.selected[0]?.label ?? '').trim();
2388
+ if (trimmed.toLowerCase() !== selectedLabel.toLowerCase()) {
2389
+ const previous = this.selected[0];
2390
+ this.selected = [];
2391
+ this.selectedItems.emit(this.selected);
2392
+ this.selectionChange.emit(this.selected);
2393
+ if (previous) {
2394
+ this.onUnselect.emit(previous);
2395
+ }
2396
+ this.propagateControlValue();
2397
+ }
2398
+ }
2399
+ if (this.chips && this.multiSelect) {
2400
+ const tokens = this.splitBySeparators(value);
2401
+ if (tokens.tokensToAdd.length) {
2402
+ for (const token of tokens.tokensToAdd) {
2403
+ this.addToken(token);
2404
+ }
2405
+ }
2406
+ this.query = tokens.remaining;
2407
+ }
2408
+ else {
2409
+ this.query = value;
2410
+ }
2128
2411
  this.onChange.emit(value);
2412
+ // Input events imply the user is interacting with the field; treat as focused so the overlay can open immediately.
2413
+ this.inputFocused = true;
2129
2414
  if (this.debounceId)
2130
2415
  clearTimeout(this.debounceId);
2131
2416
  if (this.debounceTime <= 0) {
2132
2417
  this.applyFilter(value);
2418
+ this.completeMethod.emit(value);
2133
2419
  }
2134
2420
  else {
2135
- this.debounceId = setTimeout(() => this.applyFilter(value), this.debounceTime);
2421
+ this.debounceId = setTimeout(() => {
2422
+ this.applyFilter(value);
2423
+ this.completeMethod.emit(value);
2424
+ this.cdr.markForCheck();
2425
+ }, this.debounceTime);
2136
2426
  }
2137
2427
  }
2428
+ onNativeInput(event) {
2429
+ const target = event.target;
2430
+ const value = target?.value ?? '';
2431
+ if (this.multiSelect && !this.chips && this.selected.length) {
2432
+ const prefix = this.selected.map((s) => s.label).join(', ');
2433
+ const normalizedPrefix = prefix.trim();
2434
+ if (normalizedPrefix && value.toLowerCase().startsWith(normalizedPrefix.toLowerCase())) {
2435
+ let rest = value.slice(normalizedPrefix.length);
2436
+ rest = rest.replace(/^[\s,]+/, '');
2437
+ this.onInput(rest);
2438
+ return;
2439
+ }
2440
+ }
2441
+ this.onInput(value);
2442
+ }
2138
2443
  onKeydown(event) {
2444
+ if (this.disabled)
2445
+ return;
2446
+ this.onInputKeydown.emit(event);
2447
+ if (this.chips && this.multiSelect && event.key === 'Tab' && this.updateOnTab) {
2448
+ event.preventDefault();
2449
+ this.addToken((this.query || '').trim());
2450
+ this.focusInput();
2451
+ return;
2452
+ }
2139
2453
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
2140
2454
  event.preventDefault();
2141
- this.focusSibling(event.key === 'ArrowDown' ? 1 : -1);
2455
+ this.moveActive(event.key === 'ArrowDown' ? 1 : -1);
2142
2456
  }
2143
2457
  else if (event.key === 'Enter') {
2144
2458
  event.preventDefault();
2145
- const first = this.visible[0];
2146
- if (first)
2147
- this.selectItem(first);
2459
+ const active = this.visible[this.activeIndex] || this.visible[0];
2460
+ if (active)
2461
+ this.selectItem(active);
2462
+ }
2463
+ else if (event.key === 'Escape') {
2464
+ if (this.overlayVisible) {
2465
+ event.preventDefault();
2466
+ this.hideOverlay(event);
2467
+ }
2468
+ }
2469
+ }
2470
+ onKeyup(event) {
2471
+ this.onKeyUp.emit(event);
2472
+ }
2473
+ onInputFocus(event) {
2474
+ if (this.disabled)
2475
+ return;
2476
+ this.onFocus.emit(event);
2477
+ this.inputFocused = true;
2478
+ if (this.multiSelect && !this.overlayVisible) {
2479
+ // When multi-select is displaying the selection summary, start searching with a clean query.
2480
+ this.query = '';
2481
+ // After a multi-select session, re-opening the field should show the list again even with an empty query.
2482
+ if (!this.chips && this.selected.length) {
2483
+ this.keepOpenOnEmpty = true;
2484
+ this.applyFilter('');
2485
+ this.activatePreferredOption();
2486
+ return;
2487
+ }
2488
+ }
2489
+ // Opening behavior: only show overlay when threshold met, or dropdown button explicitly used.
2490
+ if (this.query.length >= this.characterTyped) {
2491
+ this.showOverlay(event);
2492
+ }
2493
+ }
2494
+ onInputBlur(event) {
2495
+ this.onBlur.emit(event);
2496
+ this.onControlTouched();
2497
+ this.inputFocused = false;
2498
+ // Do not close on blur directly: scrolling/clicking within the overlay can blur the input.
2499
+ // Closing/commit is handled by outside click (document:mousedown) and focus moving outside (document:focusin).
2500
+ if (this.chips && this.multiSelect && this.updateOnBlur) {
2501
+ this.addToken((this.query || '').trim());
2148
2502
  }
2149
2503
  }
2150
2504
  onScroll(event) {
@@ -2152,8 +2506,14 @@ class NgbTypeaheadComponent {
2152
2506
  const scrollTop = target.scrollTop;
2153
2507
  this.onScrollEvent.emit();
2154
2508
  this.updateViewport(scrollTop);
2509
+ const remaining = target.scrollHeight - (target.scrollTop + target.clientHeight);
2510
+ if (remaining < 48) {
2511
+ this.onLazyLoad.emit();
2512
+ }
2155
2513
  }
2156
2514
  selectItem(item) {
2515
+ if (this.disabled)
2516
+ return;
2157
2517
  if (!item || item.disabled)
2158
2518
  return;
2159
2519
  this.validateItem(item);
@@ -2161,29 +2521,118 @@ class NgbTypeaheadComponent {
2161
2521
  const exists = this.selected.find((s) => s.id === item.id);
2162
2522
  if (exists) {
2163
2523
  this.selected = this.selected.filter((s) => s.id !== item.id);
2524
+ this.onUnselect.emit(item);
2164
2525
  }
2165
2526
  else {
2166
2527
  this.selected = [...this.selected, item];
2528
+ this.onSelect.emit(item);
2529
+ }
2530
+ this.propagateControlValue();
2531
+ // After selecting/unselecting, show the chosen values in the input and keep the panel open.
2532
+ this.keepOpenOnEmpty = true;
2533
+ if (this.chips) {
2534
+ this.query = '';
2535
+ }
2536
+ else {
2537
+ this.query = '';
2167
2538
  }
2539
+ this.applyFilter('');
2168
2540
  }
2169
2541
  else {
2542
+ if (this.selected.length && this.selected[0]?.id !== item.id) {
2543
+ this.onUnselect.emit(this.selected[0]);
2544
+ }
2170
2545
  this.selected = [item];
2546
+ this.query = item.label;
2547
+ if (this.inputEl?.nativeElement) {
2548
+ this.inputEl.nativeElement.value = item.label;
2549
+ }
2550
+ this.onSelect.emit(item);
2551
+ this.propagateControlValue();
2552
+ this.hideOverlay();
2171
2553
  }
2172
2554
  this.selectedItems.emit(this.selected);
2173
2555
  this.selectionChange.emit(this.selected);
2556
+ this.cdr.markForCheck();
2557
+ }
2558
+ focusInput() {
2559
+ this.inputEl?.nativeElement?.focus();
2560
+ }
2561
+ removeSelected(item) {
2562
+ if (this.disabled)
2563
+ return;
2564
+ if (!item)
2565
+ return;
2566
+ const exists = this.selected.find((s) => s.id === item.id);
2567
+ if (!exists)
2568
+ return;
2569
+ this.selected = this.selected.filter((s) => s.id !== item.id);
2570
+ this.onUnselect.emit(item);
2571
+ this.propagateControlValue();
2572
+ this.selectedItems.emit(this.selected);
2573
+ this.selectionChange.emit(this.selected);
2574
+ this.cdr.markForCheck();
2174
2575
  }
2175
2576
  clearSelection() {
2577
+ const previous = [...this.selected];
2176
2578
  this.selected = [];
2177
2579
  this.selectedItems.emit(this.selected);
2178
2580
  this.selectionChange.emit(this.selected);
2581
+ if (!this.multiSelect && previous[0]) {
2582
+ this.onUnselect.emit(previous[0]);
2583
+ }
2584
+ this.propagateControlValue();
2585
+ this.cdr.markForCheck();
2179
2586
  }
2180
2587
  isSelected(item) {
2181
2588
  return this.selected.some((s) => s.id === item.id);
2182
2589
  }
2183
2590
  trackById = (_, item) => item.id;
2591
+ getOptionId(index) {
2592
+ return `${this.overlayId}-opt-${index}`;
2593
+ }
2594
+ onDropdownButtonClick(event) {
2595
+ if (this.disabled)
2596
+ return;
2597
+ this.onDropdownClick.emit(event);
2598
+ if (this.overlayVisible) {
2599
+ this.hideOverlay(event);
2600
+ this.openedByDropdown = false;
2601
+ }
2602
+ else {
2603
+ this.openedByDropdown = true;
2604
+ if (this.scroller?.nativeElement) {
2605
+ this.scroller.nativeElement.scrollTop = 0;
2606
+ }
2607
+ this.showOverlay(event, true);
2608
+ // Show all items when the dropdown button is used, regardless of current query.
2609
+ this.applyFilter('');
2610
+ this.activatePreferredOption();
2611
+ }
2612
+ this.inputEl?.nativeElement.focus();
2613
+ this.cdr.markForCheck();
2614
+ }
2615
+ onClearClick(event) {
2616
+ if (this.disabled)
2617
+ return;
2618
+ this.onClear.emit(event);
2619
+ this.query = '';
2620
+ this.clearSelection();
2621
+ this.applyFilter('');
2622
+ this.hideOverlay(event);
2623
+ this.inputEl?.nativeElement.focus();
2624
+ this.cdr.markForCheck();
2625
+ }
2184
2626
  applyFilter(value) {
2185
2627
  const term = (value || '').toLowerCase();
2186
- const meetsThreshold = term.length >= this.characterTyped;
2628
+ if (term !== this.lastFilterTerm) {
2629
+ this.lastFilterTerm = term;
2630
+ if (this.scroller?.nativeElement) {
2631
+ this.scroller.nativeElement.scrollTop = 0;
2632
+ }
2633
+ }
2634
+ const meetsThreshold = term.length >= this.characterTyped || this.openedByDropdown || (this.multiSelect && this.keepOpenOnEmpty);
2635
+ this.hasSearched = term.length >= this.characterTyped || this.openedByDropdown;
2187
2636
  const base = meetsThreshold ? this.data.filter((d) => (d.label || '').toLowerCase().includes(term)) : [];
2188
2637
  const limited = this.limit ? base.slice(0, this.limit) : base;
2189
2638
  this.filtered = limited;
@@ -2191,6 +2640,13 @@ class NgbTypeaheadComponent {
2191
2640
  this.selectItem(limited[0]);
2192
2641
  }
2193
2642
  this.updateViewport(this.scroller?.nativeElement.scrollTop || 0);
2643
+ if (meetsThreshold && !this.overlayVisible && (this.inputFocused || this.openedByDropdown)) {
2644
+ this.showOverlay();
2645
+ }
2646
+ if (!meetsThreshold && this.overlayVisible && !this.openedByDropdown) {
2647
+ this.hideOverlay();
2648
+ }
2649
+ this.cdr.markForCheck();
2194
2650
  }
2195
2651
  updateViewport(scrollTop) {
2196
2652
  const total = this.filtered.length;
@@ -2200,140 +2656,454 @@ class NgbTypeaheadComponent {
2200
2656
  this.beforePadding = start * this.itemHeight;
2201
2657
  this.afterPadding = Math.max(total - end, 0) * this.itemHeight;
2202
2658
  this.visible = this.filtered.slice(start, end);
2659
+ this.activeIndex = this.visible.length ? Math.min(this.activeIndex, this.visible.length - 1) : -1;
2203
2660
  }
2204
- focusSibling(direction) {
2205
- const buttons = this.scroller?.nativeElement.querySelectorAll('button.list-group-item');
2206
- if (!buttons || buttons.length === 0)
2661
+ activatePreferredOption() {
2662
+ if (!this.filtered.length || !this.scroller?.nativeElement) {
2663
+ this.activeIndex = -1;
2207
2664
  return;
2208
- const active = document.activeElement;
2209
- const index = Array.prototype.indexOf.call(buttons, active);
2210
- const next = buttons[index + direction] || buttons[(index + direction + buttons.length) % buttons.length];
2211
- next?.focus();
2665
+ }
2666
+ const trimmed = (this.query || '').trim().toLowerCase();
2667
+ let preferredIndex = -1;
2668
+ if (trimmed) {
2669
+ preferredIndex = this.filtered.findIndex((d) => (d.label || '').trim().toLowerCase() === trimmed);
2670
+ }
2671
+ if (preferredIndex === -1 && this.selected.length) {
2672
+ const firstSelected = this.selected[0];
2673
+ preferredIndex = this.filtered.findIndex((d) => d.id === firstSelected.id);
2674
+ }
2675
+ if (preferredIndex === -1) {
2676
+ preferredIndex = 0;
2677
+ }
2678
+ const scrollTop = Math.max(0, preferredIndex * this.itemHeight - this.itemHeight);
2679
+ this.scroller.nativeElement.scrollTop = scrollTop;
2680
+ this.updateViewport(scrollTop);
2681
+ const start = Math.max(Math.floor(scrollTop / this.itemHeight), 0);
2682
+ this.activeIndex = Math.max(0, Math.min(preferredIndex - start, this.visible.length - 1));
2683
+ }
2684
+ moveActive(direction) {
2685
+ if (!this.overlayVisible) {
2686
+ this.showOverlay();
2687
+ }
2688
+ if (!this.visible.length)
2689
+ return;
2690
+ const nextIndex = this.activeIndex < 0 ? 0 : (this.activeIndex + direction + this.visible.length) % this.visible.length;
2691
+ this.activeIndex = nextIndex;
2692
+ const buttons = this.scroller?.nativeElement.querySelectorAll('button.dropdown-item');
2693
+ buttons?.[this.activeIndex]?.focus();
2212
2694
  }
2213
2695
  validateItem(item) {
2214
- if (this.itemTemplate) {
2215
- if (item.id == null || (item.value == null && item.value !== 0)) {
2216
- throw new Error('Custom templates require items to provide both id and value.');
2696
+ if (item.id == null) {
2697
+ throw new Error('Typeahead items must include an id.');
2698
+ }
2699
+ }
2700
+ showOverlay(event, openedByDropdown = false) {
2701
+ this.openedByDropdown = openedByDropdown;
2702
+ if (this.overlayVisible)
2703
+ return;
2704
+ this.overlayVisible = true;
2705
+ this.activeIndex = this.visible.length ? 0 : -1;
2706
+ this.onShow.emit(event ?? new Event('show'));
2707
+ this.cdr.markForCheck();
2708
+ }
2709
+ hideOverlay(event) {
2710
+ if (!this.overlayVisible)
2711
+ return;
2712
+ this.overlayVisible = false;
2713
+ this.openedByDropdown = false;
2714
+ this.activeIndex = -1;
2715
+ this.onHide.emit(event ?? new Event('hide'));
2716
+ this.cdr.markForCheck();
2717
+ }
2718
+ commitInputOnClose() {
2719
+ const trimmed = (this.query || '').trim();
2720
+ if (!trimmed) {
2721
+ return;
2722
+ }
2723
+ if (this.matchSelection) {
2724
+ const exact = this.data.find((d) => (d.label || '').toLowerCase() === trimmed.toLowerCase());
2725
+ if (!exact) {
2726
+ this.query = '';
2727
+ if (!this.multiSelect) {
2728
+ this.clearSelection();
2729
+ }
2730
+ }
2731
+ else if (!this.multiSelect) {
2732
+ this.selected = [exact];
2733
+ this.selectionChange.emit(this.selected);
2734
+ this.selectedItems.emit(this.selected);
2735
+ this.propagateControlValue();
2217
2736
  }
2737
+ return;
2738
+ }
2739
+ if (this.updateOnBlur && !this.multiSelect && this.selected.length === 0) {
2740
+ const added = { id: trimmed, label: trimmed, value: trimmed };
2741
+ this.selected = [added];
2742
+ this.onAdd.emit(added);
2743
+ this.selectionChange.emit(this.selected);
2744
+ this.selectedItems.emit(this.selected);
2745
+ this.propagateControlValue();
2746
+ }
2747
+ }
2748
+ splitBySeparators(value) {
2749
+ const separators = Array.isArray(this.separator) ? this.separator : [this.separator];
2750
+ const safe = separators.filter((s) => s != null && `${s}`.length > 0).map((s) => `${s}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
2751
+ if (!safe.length) {
2752
+ return { tokensToAdd: [], remaining: value };
2753
+ }
2754
+ const regex = new RegExp(`(?:${safe.join('|')})`);
2755
+ if (!regex.test(value)) {
2756
+ return { tokensToAdd: [], remaining: value };
2757
+ }
2758
+ const parts = value.split(new RegExp(`(?:${safe.join('|')})`, 'g')).map((p) => p.trim());
2759
+ const endsWithSeparator = separators.some((sep) => value.endsWith(sep));
2760
+ const tokensToAdd = (endsWithSeparator ? parts : parts.slice(0, -1)).filter(Boolean);
2761
+ const remaining = endsWithSeparator ? '' : (parts[parts.length - 1] || '');
2762
+ return { tokensToAdd, remaining };
2763
+ }
2764
+ addToken(raw) {
2765
+ const token = (raw || '').trim();
2766
+ if (!this.chips || !this.multiSelect)
2767
+ return;
2768
+ if (!token)
2769
+ return;
2770
+ const match = this.data.find((d) => (d.label || '').trim().toLowerCase() === token.toLowerCase());
2771
+ const item = match ?? { id: token, label: token, value: token };
2772
+ if (this.matchSelection && !match) {
2773
+ this.query = '';
2774
+ return;
2218
2775
  }
2776
+ if (this.selected.some((s) => s.id === item.id)) {
2777
+ this.query = '';
2778
+ return;
2779
+ }
2780
+ this.selected = [...this.selected, item];
2781
+ this.onAdd.emit(item);
2782
+ this.propagateControlValue();
2783
+ this.selectedItems.emit(this.selected);
2784
+ this.selectionChange.emit(this.selected);
2785
+ this.query = '';
2786
+ this.keepOpenOnEmpty = true;
2787
+ this.applyFilter('');
2219
2788
  }
2220
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2221
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTypeaheadComponent, isStandalone: true, selector: "ngb-typeahead", inputs: { data: "data", debounceTime: "debounceTime", characterTyped: "characterTyped", limit: "limit", selectExact: "selectExact", multiSelect: "multiSelect", i18n: "i18n" }, outputs: { selectedItems: "selectedItems", selectionChange: "selectionChange", onChange: "onChange", onScrollEvent: "onScrollEvent" }, queries: [{ propertyName: "itemTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, static: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "itemButtons", predicate: ["itemButton"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
2222
- <div class="mb-2">
2223
- <input
2224
- #inputEl
2225
- type="text"
2226
- class="form-control"
2227
- [attr.placeholder]="i18n?.placeholder || 'Type to search'"
2228
- [ngModel]="query"
2229
- (ngModelChange)="onInput($event)"
2230
- (keydown)="onKeydown($event)"
2231
- aria-autocomplete="list"
2232
- role="combobox"
2233
- [attr.aria-expanded]="filtered.length > 0"
2234
- />
2235
- </div>
2789
+ propagateControlValue() {
2790
+ if (this.multiSelect) {
2791
+ const next = this.selected.map((item) => (item.value !== undefined ? item.value : item));
2792
+ this.controlValue = next;
2793
+ this.onControlChange(next);
2794
+ return;
2795
+ }
2796
+ const first = this.selected[0];
2797
+ const next = first ? (first.value !== undefined ? first.value : first) : null;
2798
+ this.controlValue = next;
2799
+ this.onControlChange(next);
2800
+ }
2801
+ coerceToItem(value) {
2802
+ if (value == null)
2803
+ return null;
2804
+ if (typeof value === 'object' && value.id != null && value.label != null) {
2805
+ return value;
2806
+ }
2807
+ const match = this.data.find((d) => d.value === value || d.id === value || (d.label || '').toLowerCase() === `${value}`.toLowerCase());
2808
+ if (match)
2809
+ return match;
2810
+ return { id: `${value}`, label: `${value}`, value };
2811
+ }
2812
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2813
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTypeaheadComponent, isStandalone: true, selector: "ngb-typeahead", inputs: { data: "data", debounceTime: "debounceTime", characterTyped: "characterTyped", limit: "limit", selectExact: "selectExact", multiSelect: "multiSelect", matchSelection: "matchSelection", showDropdownButton: "showDropdownButton", showClearButton: "showClearButton", updateOnBlur: "updateOnBlur", updateOnTab: "updateOnTab", separator: "separator", chips: "chips", itemTemplate: "itemTemplate", i18n: "i18n" }, outputs: { completeMethod: "completeMethod", onSelect: "onSelect", onUnselect: "onUnselect", onAdd: "onAdd", onFocus: "onFocus", onBlur: "onBlur", onDropdownClick: "onDropdownClick", onClear: "onClear", onInputKeydown: "onInputKeydown", onKeyUp: "onKeyUp", onShow: "onShow", onHide: "onHide", onLazyLoad: "onLazyLoad", selectedItems: "selectedItems", selectionChange: "selectionChange", onChange: "onChange", onScrollEvent: "onScrollEvent" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)", "document:focusin": "onDocumentFocusIn($event)" } }, providers: [
2814
+ {
2815
+ provide: NG_VALUE_ACCESSOR,
2816
+ useExisting: forwardRef(() => NgbTypeaheadComponent),
2817
+ multi: true,
2818
+ },
2819
+ ], queries: [{ propertyName: "projectedItemTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, static: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true }, { propertyName: "itemButtons", predicate: ["itemButton"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
2820
+ <div class="typeahead-root">
2821
+ <div class="input-group mb-2">
2822
+ <ng-container *ngIf="chips && multiSelect; else plainInput">
2823
+ <div class="form-control typeahead-chips-control" (click)="disabled ? null : focusInput()">
2824
+ <ngb-chips
2825
+ [items]="selected"
2826
+ [removable]="true"
2827
+ (remove)="removeSelected($event)"
2828
+ ariaLabel="Selected items"
2829
+ ></ngb-chips>
2830
+ <input
2831
+ #inputEl
2832
+ type="text"
2833
+ [disabled]="disabled"
2834
+ [attr.placeholder]="selected.length ? '' : (i18n?.placeholder || 'Type to search')"
2835
+ [value]="query"
2836
+ (input)="onNativeInput($event)"
2837
+ (focus)="onInputFocus($event)"
2838
+ (blur)="onInputBlur($event)"
2839
+ (keydown)="onKeydown($event)"
2840
+ (keyup)="onKeyup($event)"
2841
+ aria-autocomplete="list"
2842
+ role="combobox"
2843
+ [attr.aria-expanded]="overlayVisible"
2844
+ [attr.aria-controls]="overlayId"
2845
+ [attr.aria-activedescendant]="activeDescendantId"
2846
+ />
2847
+ </div>
2848
+ </ng-container>
2849
+ <ng-template #plainInput>
2850
+ <input
2851
+ #inputEl
2852
+ type="text"
2853
+ class="form-control"
2854
+ [disabled]="disabled"
2855
+ [attr.placeholder]="i18n?.placeholder || 'Type to search'"
2856
+ [value]="displayQuery"
2857
+ (input)="onNativeInput($event)"
2858
+ (focus)="onInputFocus($event)"
2859
+ (blur)="onInputBlur($event)"
2860
+ (keydown)="onKeydown($event)"
2861
+ (keyup)="onKeyup($event)"
2862
+ aria-autocomplete="list"
2863
+ role="combobox"
2864
+ [attr.aria-expanded]="overlayVisible"
2865
+ [attr.aria-controls]="overlayId"
2866
+ [attr.aria-activedescendant]="activeDescendantId"
2867
+ />
2868
+ </ng-template>
2236
2869
 
2237
- <div
2238
- #scroller
2239
- class="border rounded list-group"
2240
- style="max-height: 240px; overflow: auto;"
2241
- role="listbox"
2242
- (scroll)="onScroll($event)"
2243
- >
2244
- <div [style.height.px]="beforePadding"></div>
2245
- <button
2246
- *ngFor="let item of visible; trackBy: trackById"
2247
- type="button"
2248
- class="list-group-item list-group-item-action d-flex align-items-center justify-content-between"
2249
- [class.active]="isSelected(item)"
2250
- [attr.aria-selected]="isSelected(item)"
2251
- (click)="selectItem(item)"
2252
- [disabled]="item.disabled"
2870
+ <button
2871
+ *ngIf="showClearButton && (query || (!multiSelect && selected.length))"
2872
+ type="button"
2873
+ class="btn btn-outline-secondary"
2874
+ (click)="onClearClick($event)"
2875
+ [disabled]="disabled"
2876
+ [attr.aria-label]="i18n?.clear || 'Clear'"
2877
+ >
2878
+ &times;
2879
+ </button>
2880
+
2881
+ <button
2882
+ *ngIf="showDropdownButton"
2883
+ type="button"
2884
+ class="btn btn-outline-secondary"
2885
+ (click)="onDropdownButtonClick($event)"
2886
+ [disabled]="disabled"
2887
+ [attr.aria-label]="i18n?.dropdownButtonLabel || 'Toggle suggestions'"
2888
+ >
2889
+ &#9662;
2890
+ </button>
2891
+ </div>
2892
+
2893
+ <div
2894
+ #scroller
2895
+ class="dropdown-menu w-100 p-0 typeahead-panel"
2896
+ [class.show]="overlayVisible"
2897
+ [id]="overlayId"
2898
+ role="listbox"
2899
+ (scroll)="onScroll($event)"
2253
2900
  >
2254
- <ng-container *ngIf="itemTemplate; else defaultTpl" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
2255
- <ng-template #defaultTpl>
2256
- <span>{{ item.label }}</span>
2257
- <span *ngIf="multiSelect && isSelected(item)" class="badge bg-secondary">✓</span>
2258
- </ng-template>
2259
- </button>
2260
- <div [style.height.px]="afterPadding"></div>
2901
+ <div [style.height.px]="beforePadding"></div>
2261
2902
 
2262
- <div *ngIf="!filtered.length" class="list-group-item text-muted text-center">
2263
- {{ i18n?.noResults || 'No results' }}
2903
+ <button
2904
+ *ngFor="let item of visible; trackBy: trackById; let idx = index"
2905
+ type="button"
2906
+ class="dropdown-item"
2907
+ [class.active]="idx === activeIndex"
2908
+ [class.fw-semibold]="isSelected(item)"
2909
+ [attr.aria-selected]="isSelected(item)"
2910
+ [attr.id]="getOptionId(idx)"
2911
+ (click)="selectItem(item)"
2912
+ [disabled]="item.disabled"
2913
+ >
2914
+ <div class="typeahead-item">
2915
+ <span *ngIf="multiSelect" class="typeahead-item-checkbox">
2916
+ <input
2917
+ type="checkbox"
2918
+ class="form-check-input m-0"
2919
+ [checked]="isSelected(item)"
2920
+ [disabled]="item.disabled"
2921
+ tabindex="-1"
2922
+ aria-hidden="true"
2923
+ />
2924
+ </span>
2925
+ <ng-container *ngIf="resolvedItemTemplate as tpl; else defaultTpl">
2926
+ <ng-container
2927
+ [ngTemplateOutlet]="tpl"
2928
+ [ngTemplateOutletContext]="{
2929
+ $implicit: item,
2930
+ item: item,
2931
+ query: query,
2932
+ selected: isSelected(item),
2933
+ index: idx
2934
+ }"
2935
+ ></ng-container>
2936
+ </ng-container>
2937
+ <ng-template #defaultTpl>
2938
+ <span>{{ item.label }}</span>
2939
+ </ng-template>
2940
+ </div>
2941
+ </button>
2942
+
2943
+ <div [style.height.px]="afterPadding"></div>
2944
+
2945
+ <div *ngIf="showNoResults" class="dropdown-item text-muted text-center">
2946
+ {{ i18n?.noResults || 'No results' }}
2947
+ </div>
2264
2948
  </div>
2265
- </div>
2266
2949
 
2267
- <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
2268
- {{ selectionSummary }}
2269
- <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
2270
- {{ i18n?.clearSelection || 'Clear' }}
2271
- </button>
2950
+ <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
2951
+ {{ selectionSummary }}
2952
+ <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
2953
+ {{ i18n?.clearSelection || 'Clear' }}
2954
+ </button>
2955
+ </div>
2272
2956
  </div>
2273
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2957
+ `, isInline: true, styles: [":host{display:block}.typeahead-root{position:relative}.typeahead-chips-control{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;min-height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem}.typeahead-chips-control input{border:0;outline:0;flex:1 1 9rem;min-width:6rem;padding:0;margin:0;background:transparent}.typeahead-chips-control input:focus{outline:0;box-shadow:none}.typeahead-panel{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:1050;max-height:240px;overflow:auto;opacity:0;transform:translateY(-.25rem);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.typeahead-panel.show{opacity:1;transform:translateY(0);pointer-events:auto}.typeahead-item{display:flex;align-items:center;gap:.5rem}.typeahead-item-checkbox{display:inline-flex;align-items:center;justify-content:center;width:1.15rem;height:1.15rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgbChipsComponent, selector: "ngb-chips", inputs: ["items", "removable", "ariaLabel", "removeLabel"], outputs: ["remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2274
2958
  }
2275
2959
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, decorators: [{
2276
2960
  type: Component,
2277
- args: [{
2278
- selector: 'ngb-typeahead',
2279
- standalone: true,
2280
- imports: [CommonModule, FormsModule],
2281
- changeDetection: ChangeDetectionStrategy.OnPush,
2282
- template: `
2283
- <div class="mb-2">
2284
- <input
2285
- #inputEl
2286
- type="text"
2287
- class="form-control"
2288
- [attr.placeholder]="i18n?.placeholder || 'Type to search'"
2289
- [ngModel]="query"
2290
- (ngModelChange)="onInput($event)"
2291
- (keydown)="onKeydown($event)"
2292
- aria-autocomplete="list"
2293
- role="combobox"
2294
- [attr.aria-expanded]="filtered.length > 0"
2295
- />
2296
- </div>
2961
+ args: [{ selector: 'ngb-typeahead', standalone: true, imports: [CommonModule, NgbChipsComponent], providers: [
2962
+ {
2963
+ provide: NG_VALUE_ACCESSOR,
2964
+ useExisting: forwardRef(() => NgbTypeaheadComponent),
2965
+ multi: true,
2966
+ },
2967
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2968
+ <div class="typeahead-root">
2969
+ <div class="input-group mb-2">
2970
+ <ng-container *ngIf="chips && multiSelect; else plainInput">
2971
+ <div class="form-control typeahead-chips-control" (click)="disabled ? null : focusInput()">
2972
+ <ngb-chips
2973
+ [items]="selected"
2974
+ [removable]="true"
2975
+ (remove)="removeSelected($event)"
2976
+ ariaLabel="Selected items"
2977
+ ></ngb-chips>
2978
+ <input
2979
+ #inputEl
2980
+ type="text"
2981
+ [disabled]="disabled"
2982
+ [attr.placeholder]="selected.length ? '' : (i18n?.placeholder || 'Type to search')"
2983
+ [value]="query"
2984
+ (input)="onNativeInput($event)"
2985
+ (focus)="onInputFocus($event)"
2986
+ (blur)="onInputBlur($event)"
2987
+ (keydown)="onKeydown($event)"
2988
+ (keyup)="onKeyup($event)"
2989
+ aria-autocomplete="list"
2990
+ role="combobox"
2991
+ [attr.aria-expanded]="overlayVisible"
2992
+ [attr.aria-controls]="overlayId"
2993
+ [attr.aria-activedescendant]="activeDescendantId"
2994
+ />
2995
+ </div>
2996
+ </ng-container>
2997
+ <ng-template #plainInput>
2998
+ <input
2999
+ #inputEl
3000
+ type="text"
3001
+ class="form-control"
3002
+ [disabled]="disabled"
3003
+ [attr.placeholder]="i18n?.placeholder || 'Type to search'"
3004
+ [value]="displayQuery"
3005
+ (input)="onNativeInput($event)"
3006
+ (focus)="onInputFocus($event)"
3007
+ (blur)="onInputBlur($event)"
3008
+ (keydown)="onKeydown($event)"
3009
+ (keyup)="onKeyup($event)"
3010
+ aria-autocomplete="list"
3011
+ role="combobox"
3012
+ [attr.aria-expanded]="overlayVisible"
3013
+ [attr.aria-controls]="overlayId"
3014
+ [attr.aria-activedescendant]="activeDescendantId"
3015
+ />
3016
+ </ng-template>
2297
3017
 
2298
- <div
2299
- #scroller
2300
- class="border rounded list-group"
2301
- style="max-height: 240px; overflow: auto;"
2302
- role="listbox"
2303
- (scroll)="onScroll($event)"
2304
- >
2305
- <div [style.height.px]="beforePadding"></div>
2306
- <button
2307
- *ngFor="let item of visible; trackBy: trackById"
2308
- type="button"
2309
- class="list-group-item list-group-item-action d-flex align-items-center justify-content-between"
2310
- [class.active]="isSelected(item)"
2311
- [attr.aria-selected]="isSelected(item)"
2312
- (click)="selectItem(item)"
2313
- [disabled]="item.disabled"
3018
+ <button
3019
+ *ngIf="showClearButton && (query || (!multiSelect && selected.length))"
3020
+ type="button"
3021
+ class="btn btn-outline-secondary"
3022
+ (click)="onClearClick($event)"
3023
+ [disabled]="disabled"
3024
+ [attr.aria-label]="i18n?.clear || 'Clear'"
3025
+ >
3026
+ &times;
3027
+ </button>
3028
+
3029
+ <button
3030
+ *ngIf="showDropdownButton"
3031
+ type="button"
3032
+ class="btn btn-outline-secondary"
3033
+ (click)="onDropdownButtonClick($event)"
3034
+ [disabled]="disabled"
3035
+ [attr.aria-label]="i18n?.dropdownButtonLabel || 'Toggle suggestions'"
3036
+ >
3037
+ &#9662;
3038
+ </button>
3039
+ </div>
3040
+
3041
+ <div
3042
+ #scroller
3043
+ class="dropdown-menu w-100 p-0 typeahead-panel"
3044
+ [class.show]="overlayVisible"
3045
+ [id]="overlayId"
3046
+ role="listbox"
3047
+ (scroll)="onScroll($event)"
2314
3048
  >
2315
- <ng-container *ngIf="itemTemplate; else defaultTpl" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
2316
- <ng-template #defaultTpl>
2317
- <span>{{ item.label }}</span>
2318
- <span *ngIf="multiSelect && isSelected(item)" class="badge bg-secondary">✓</span>
2319
- </ng-template>
2320
- </button>
2321
- <div [style.height.px]="afterPadding"></div>
3049
+ <div [style.height.px]="beforePadding"></div>
2322
3050
 
2323
- <div *ngIf="!filtered.length" class="list-group-item text-muted text-center">
2324
- {{ i18n?.noResults || 'No results' }}
3051
+ <button
3052
+ *ngFor="let item of visible; trackBy: trackById; let idx = index"
3053
+ type="button"
3054
+ class="dropdown-item"
3055
+ [class.active]="idx === activeIndex"
3056
+ [class.fw-semibold]="isSelected(item)"
3057
+ [attr.aria-selected]="isSelected(item)"
3058
+ [attr.id]="getOptionId(idx)"
3059
+ (click)="selectItem(item)"
3060
+ [disabled]="item.disabled"
3061
+ >
3062
+ <div class="typeahead-item">
3063
+ <span *ngIf="multiSelect" class="typeahead-item-checkbox">
3064
+ <input
3065
+ type="checkbox"
3066
+ class="form-check-input m-0"
3067
+ [checked]="isSelected(item)"
3068
+ [disabled]="item.disabled"
3069
+ tabindex="-1"
3070
+ aria-hidden="true"
3071
+ />
3072
+ </span>
3073
+ <ng-container *ngIf="resolvedItemTemplate as tpl; else defaultTpl">
3074
+ <ng-container
3075
+ [ngTemplateOutlet]="tpl"
3076
+ [ngTemplateOutletContext]="{
3077
+ $implicit: item,
3078
+ item: item,
3079
+ query: query,
3080
+ selected: isSelected(item),
3081
+ index: idx
3082
+ }"
3083
+ ></ng-container>
3084
+ </ng-container>
3085
+ <ng-template #defaultTpl>
3086
+ <span>{{ item.label }}</span>
3087
+ </ng-template>
3088
+ </div>
3089
+ </button>
3090
+
3091
+ <div [style.height.px]="afterPadding"></div>
3092
+
3093
+ <div *ngIf="showNoResults" class="dropdown-item text-muted text-center">
3094
+ {{ i18n?.noResults || 'No results' }}
3095
+ </div>
2325
3096
  </div>
2326
- </div>
2327
3097
 
2328
- <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
2329
- {{ selectionSummary }}
2330
- <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
2331
- {{ i18n?.clearSelection || 'Clear' }}
2332
- </button>
3098
+ <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
3099
+ {{ selectionSummary }}
3100
+ <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
3101
+ {{ i18n?.clearSelection || 'Clear' }}
3102
+ </button>
3103
+ </div>
2333
3104
  </div>
2334
- `,
2335
- }]
2336
- }], propDecorators: { data: [{
3105
+ `, styles: [":host{display:block}.typeahead-root{position:relative}.typeahead-chips-control{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;min-height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem}.typeahead-chips-control input{border:0;outline:0;flex:1 1 9rem;min-width:6rem;padding:0;margin:0;background:transparent}.typeahead-chips-control input:focus{outline:0;box-shadow:none}.typeahead-panel{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:1050;max-height:240px;overflow:auto;opacity:0;transform:translateY(-.25rem);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.typeahead-panel.show{opacity:1;transform:translateY(0);pointer-events:auto}.typeahead-item{display:flex;align-items:center;gap:.5rem}.typeahead-item-checkbox{display:inline-flex;align-items:center;justify-content:center;width:1.15rem;height:1.15rem}\n"] }]
3106
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { data: [{
2337
3107
  type: Input
2338
3108
  }], debounceTime: [{
2339
3109
  type: Input
@@ -2345,11 +3115,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2345
3115
  type: Input
2346
3116
  }], multiSelect: [{
2347
3117
  type: Input
2348
- }], i18n: [{
3118
+ }], matchSelection: [{
3119
+ type: Input
3120
+ }], showDropdownButton: [{
3121
+ type: Input
3122
+ }], showClearButton: [{
3123
+ type: Input
3124
+ }], updateOnBlur: [{
3125
+ type: Input
3126
+ }], updateOnTab: [{
3127
+ type: Input
3128
+ }], separator: [{
3129
+ type: Input
3130
+ }], chips: [{
2349
3131
  type: Input
2350
3132
  }], itemTemplate: [{
3133
+ type: Input
3134
+ }], i18n: [{
3135
+ type: Input
3136
+ }], projectedItemTemplate: [{
2351
3137
  type: ContentChild,
2352
3138
  args: [TemplateRef]
3139
+ }], completeMethod: [{
3140
+ type: Output
3141
+ }], onSelect: [{
3142
+ type: Output
3143
+ }], onUnselect: [{
3144
+ type: Output
3145
+ }], onAdd: [{
3146
+ type: Output
3147
+ }], onFocus: [{
3148
+ type: Output
3149
+ }], onBlur: [{
3150
+ type: Output
3151
+ }], onDropdownClick: [{
3152
+ type: Output
3153
+ }], onClear: [{
3154
+ type: Output
3155
+ }], onInputKeydown: [{
3156
+ type: Output
3157
+ }], onKeyUp: [{
3158
+ type: Output
3159
+ }], onShow: [{
3160
+ type: Output
3161
+ }], onHide: [{
3162
+ type: Output
3163
+ }], onLazyLoad: [{
3164
+ type: Output
2353
3165
  }], selectedItems: [{
2354
3166
  type: Output
2355
3167
  }], selectionChange: [{
@@ -2363,10 +3175,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2363
3175
  args: ['scroller', { static: true }]
2364
3176
  }], inputEl: [{
2365
3177
  type: ViewChild,
2366
- args: ['inputEl', { static: true }]
3178
+ args: ['inputEl']
2367
3179
  }], itemButtons: [{
2368
3180
  type: ViewChildren,
2369
3181
  args: ['itemButton']
3182
+ }], onDocumentMouseDown: [{
3183
+ type: HostListener,
3184
+ args: ['document:mousedown', ['$event']]
3185
+ }], onDocumentFocusIn: [{
3186
+ type: HostListener,
3187
+ args: ['document:focusin', ['$event']]
2370
3188
  }] } });
2371
3189
 
2372
3190
  class NgbTreeComponent {
@@ -2374,6 +3192,10 @@ class NgbTreeComponent {
2374
3192
  type = 'text';
2375
3193
  showCheckbox = false;
2376
3194
  i18n;
3195
+ expandIcon = 'bi-caret-right-fill';
3196
+ collapseIcon = 'bi-caret-down-fill';
3197
+ plusIcon = 'bi-file-plus-fill';
3198
+ minusIcon = 'bi-file-minus-fill';
2377
3199
  expand = new EventEmitter();
2378
3200
  collapse = new EventEmitter();
2379
3201
  expandAll = new EventEmitter();
@@ -2454,7 +3276,7 @@ class NgbTreeComponent {
2454
3276
  return acc;
2455
3277
  }
2456
3278
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2457
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTreeComponent, isStandalone: true, selector: "ngb-tree", inputs: { nodes: "nodes", type: "type", showCheckbox: "showCheckbox", i18n: "i18n" }, outputs: { expand: "expand", collapse: "collapse", expandAll: "expandAll", collapseAll: "collapseAll", selectionChange: "selectionChange" }, viewQueries: [{ propertyName: "nodeButtons", predicate: ["nodeBtn"], descendants: true, read: ElementRef }], ngImport: i0, template: `
3279
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTreeComponent, isStandalone: true, selector: "ngb-tree", inputs: { nodes: "nodes", type: "type", showCheckbox: "showCheckbox", i18n: "i18n", expandIcon: "expandIcon", collapseIcon: "collapseIcon", plusIcon: "plusIcon", minusIcon: "minusIcon" }, outputs: { expand: "expand", collapse: "collapse", expandAll: "expandAll", collapseAll: "collapseAll", selectionChange: "selectionChange" }, viewQueries: [{ propertyName: "nodeButtons", predicate: ["nodeBtn"], descendants: true, read: ElementRef }], ngImport: i0, template: `
2458
3280
  <div class="d-flex gap-2 mb-2">
2459
3281
  <button type="button" class="btn btn-sm btn-outline-secondary" (click)="expandAllNodes()" [attr.aria-label]="i18n?.expandAll || 'Expand all'">
2460
3282
  {{ i18n?.expandAll || 'Expand all' }}
@@ -2477,21 +3299,22 @@ class NgbTreeComponent {
2477
3299
  [attr.aria-expanded]="hasChildren(node) ? !!node.expanded : null"
2478
3300
  [attr.aria-level]="level"
2479
3301
  >
2480
- <button
2481
- #nodeBtn
2482
- type="button"
2483
- class="btn btn-sm btn-link px-1"
2484
- [disabled]="!hasChildren(node)"
2485
- (click)="toggleNode(node)"
2486
- (keydown)="onNodeKeydown($event, node)"
2487
- [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
2488
- >
2489
- <span *ngIf="hasChildren(node)">
2490
- <span *ngIf="type === 'json'">{{ node.expanded ? '-' : '+' }}</span>
2491
- <span *ngIf="type !== 'json'">{{ node.expanded ? '▾' : '▸' }}</span>
2492
- </span>
2493
- <span *ngIf="!hasChildren(node)" class="text-muted">•</span>
2494
- </button>
3302
+ <ng-container *ngIf="hasChildren(node); else leafSpacer">
3303
+ <button
3304
+ #nodeBtn
3305
+ type="button"
3306
+ class="btn btn-sm px-1"
3307
+ (click)="toggleNode(node)"
3308
+ (keydown)="onNodeKeydown($event, node)"
3309
+ [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
3310
+ >
3311
+ <span *ngIf="type === 'json'" class="bi" [ngClass]="node.expanded ? minusIcon : plusIcon" aria-hidden="true"></span>
3312
+ <span *ngIf="type !== 'json'" class="bi" [ngClass]="node.expanded ? collapseIcon : expandIcon" aria-hidden="true"></span>
3313
+ </button>
3314
+ </ng-container>
3315
+ <ng-template #leafSpacer>
3316
+ <span class="d-inline-block px-1" style="width: 1.5rem;"></span>
3317
+ </ng-template>
2495
3318
 
2496
3319
  <label class="form-check d-inline-flex align-items-center gap-2 flex-grow-1 mb-0">
2497
3320
  <input
@@ -2517,7 +3340,7 @@ class NgbTreeComponent {
2517
3340
  </ng-container>
2518
3341
  </ul>
2519
3342
  </ng-template>
2520
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3343
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2521
3344
  }
2522
3345
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, decorators: [{
2523
3346
  type: Component,
@@ -2549,21 +3372,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2549
3372
  [attr.aria-expanded]="hasChildren(node) ? !!node.expanded : null"
2550
3373
  [attr.aria-level]="level"
2551
3374
  >
2552
- <button
2553
- #nodeBtn
2554
- type="button"
2555
- class="btn btn-sm btn-link px-1"
2556
- [disabled]="!hasChildren(node)"
2557
- (click)="toggleNode(node)"
2558
- (keydown)="onNodeKeydown($event, node)"
2559
- [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
2560
- >
2561
- <span *ngIf="hasChildren(node)">
2562
- <span *ngIf="type === 'json'">{{ node.expanded ? '-' : '+' }}</span>
2563
- <span *ngIf="type !== 'json'">{{ node.expanded ? '▾' : '▸' }}</span>
2564
- </span>
2565
- <span *ngIf="!hasChildren(node)" class="text-muted">•</span>
2566
- </button>
3375
+ <ng-container *ngIf="hasChildren(node); else leafSpacer">
3376
+ <button
3377
+ #nodeBtn
3378
+ type="button"
3379
+ class="btn btn-sm px-1"
3380
+ (click)="toggleNode(node)"
3381
+ (keydown)="onNodeKeydown($event, node)"
3382
+ [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
3383
+ >
3384
+ <span *ngIf="type === 'json'" class="bi" [ngClass]="node.expanded ? minusIcon : plusIcon" aria-hidden="true"></span>
3385
+ <span *ngIf="type !== 'json'" class="bi" [ngClass]="node.expanded ? collapseIcon : expandIcon" aria-hidden="true"></span>
3386
+ </button>
3387
+ </ng-container>
3388
+ <ng-template #leafSpacer>
3389
+ <span class="d-inline-block px-1" style="width: 1.5rem;"></span>
3390
+ </ng-template>
2567
3391
 
2568
3392
  <label class="form-check d-inline-flex align-items-center gap-2 flex-grow-1 mb-0">
2569
3393
  <input
@@ -2599,6 +3423,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2599
3423
  type: Input
2600
3424
  }], i18n: [{
2601
3425
  type: Input
3426
+ }], expandIcon: [{
3427
+ type: Input
3428
+ }], collapseIcon: [{
3429
+ type: Input
3430
+ }], plusIcon: [{
3431
+ type: Input
3432
+ }], minusIcon: [{
3433
+ type: Input
2602
3434
  }], expand: [{
2603
3435
  type: Output
2604
3436
  }], collapse: [{
@@ -2620,5 +3452,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2620
3452
  * Generated bundle index. Do not edit.
2621
3453
  */
2622
3454
 
2623
- export { DATAGRID_TEMPLATE_DIRECTIVES, DND_I18N, DND_LIVE_ANNOUNCE, Datagrid, ExcelExportAdapter, ExportButtonDirective, JsPdfAdapter, NgbCellTemplate, NgbDndItemDirective, NgbDndListDirective, NgbDndState, NgbEditorTemplate, NgbExportService, NgbFilterTemplate, NgbGlobalFilterTemplate, NgbLiveAnnouncer, NgbPaginationComponent, NgbRowDetailTemplate, NgbSplitterComponent, NgbStepLabelDirective, NgbStepperComponent, NgbTreeComponent, NgbTypeaheadComponent, PdfExportAdapter, XlsxAdapter, defaultDndI18n };
3455
+ export { DATAGRID_TEMPLATE_DIRECTIVES, DND_I18N, DND_LIVE_ANNOUNCE, Datagrid, ExcelExportAdapter, ExportButtonDirective, JsPdfAdapter, NgbCellTemplate, NgbChipsComponent, NgbDndItemDirective, NgbDndListDirective, NgbDndState, NgbEditorTemplate, NgbExportService, NgbFilterTemplate, NgbGlobalFilterTemplate, NgbLiveAnnouncer, NgbPaginationComponent, NgbRowDetailTemplate, NgbSplitterComponent, NgbSplitterPaneComponent, NgbStepLabelDirective, NgbStepperComponent, NgbTreeComponent, NgbTypeaheadComponent, PdfExportAdapter, XlsxAdapter, defaultDndI18n };
2624
3456
  //# sourceMappingURL=angular-bootstrap-ngbootstrap.mjs.map