Skip to content

Commit 97486cb

Browse files
added new component tree, splitte, typeahead
1 parent 2688b48 commit 97486cb

23 files changed

Lines changed: 1122 additions & 9 deletions

README.md

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ngbootstrap
1+
# @angular-bootstrap/ngbootstrap
22

33
Angular UI library providing datagrid, drag‑and‑drop, pagination, and stepper components with Bootstrap‑friendly styling.
44

@@ -8,12 +8,15 @@ Angular UI library providing datagrid, drag‑and‑drop, pagination, and steppe
88
- Drag & drop – lightweight list and item directives with keyboard‑friendly a11y helpers.
99
- Pagination – standalone Bootstrap‑styled pagination component.
1010
- Stepper – horizontal/vertical stepper with custom labels, error states, theming hooks, and keyboard support.
11+
- Splitter – resizable horizontal/vertical panes with collapsing, keyboard resizing, and ARIA semantics.
12+
- Tree – keyboard-accessible tree with optional checkboxes, JSON-style expanders, and expand/collapse helpers.
13+
- Typeahead – virtualized, debounced search with single/multi select, exact-match selection, and scroll hooks.
1114
- Angular + Bootstrap first – built for modern Angular (v17–20) and works with plain Bootstrap CSS; Material/Tailwind can be layered via custom styles.
1215

1316
## Installation
1417

1518
```bash
16-
npm install ngbootstrap
19+
npm install @angular-bootstrap/ngbootstrap
1720
```
1821

1922
Make sure your app:
@@ -33,7 +36,7 @@ All components are standalone, so you import them directly into your feature com
3336

3437
```ts
3538
import { Component } from '@angular/core';
36-
import { Datagrid } from 'ngbootstrap/datagrid';
39+
import { Datagrid } from '@angular-bootstrap/ngbootstrap/datagrid';
3740

3841
interface User {
3942
id: number;
@@ -93,7 +96,7 @@ npm install jspdf jspdf-autotable xlsx
9396

9497
```ts
9598
import { Component } from '@angular/core';
96-
import { NgbPaginationComponent } from 'ngbootstrap/pagination';
99+
import { NgbPaginationComponent } from '@angular-bootstrap/ngbootstrap/pagination';
97100

98101
@Component({
99102
standalone: true,
@@ -124,8 +127,36 @@ export class PagerComponent {
124127

125128
```ts
126129
import { Component } from '@angular/core';
127-
import { NgbStepperComponent } from 'ngbootstrap/stepper';
128-
import { NgbStepperStep } from 'ngbootstrap/stepper';
130+
import { NgbStepperComponent } from '@angular-bootstrap/ngbootstrap/stepper';
131+
import { NgbStepperStep } from '@angular-bootstrap/ngbootstrap/stepper';
132+
133+
### Splitter
134+
135+
```ts
136+
import { Component } from '@angular/core';
137+
import { NgbSplitterComponent, NgbSplitterPaneComponent } from '@angular-bootstrap/ngbootstrap/splitter';
138+
139+
@Component({
140+
standalone: true,
141+
selector: 'app-splitter',
142+
imports: [NgbSplitterComponent, NgbSplitterPaneComponent],
143+
template: `
144+
<ngb-splitter orientation="horizontal">
145+
<ngb-splitter-pane size="30%" min="200px" [collapsible]="true" (collapsedChange)="onCollapse($event)">
146+
<div class="p-3">Navigation</div>
147+
</ngb-splitter-pane>
148+
<ngb-splitter-pane>
149+
<div class="p-3">Main content</div>
150+
</ngb-splitter-pane>
151+
</ngb-splitter>
152+
`,
153+
})
154+
export class SplitterExampleComponent {
155+
onCollapse(collapsed: boolean) {
156+
// persist pane state if needed
157+
}
158+
}
159+
```
129160

130161
@Component({
131162
standalone: true,
@@ -178,7 +209,47 @@ Stepper highlights:
178209

179210
```ts
180211
import { Component } from '@angular/core';
181-
import { DndListDirective, DndItemDirective } from 'ngbootstrap/drag-drop';
212+
import { DndListDirective, DndItemDirective } from '@angular-bootstrap/ngbootstrap/drag-drop';
213+
214+
### Tree
215+
216+
```ts
217+
import { Component } from '@angular/core';
218+
import { NgbTreeComponent, NgbTreeNode } from '@angular-bootstrap/ngbootstrap/tree';
219+
220+
@Component({
221+
standalone: true,
222+
selector: 'app-tree',
223+
imports: [NgbTreeComponent],
224+
template: `
225+
<ngb-tree
226+
[nodes]="nodes"
227+
[showCheckbox]="true"
228+
type="json"
229+
(expand)="onExpand($event)"
230+
(collapse)="onCollapse($event)"
231+
(selectionChange)="onSelection($event)"
232+
></ngb-tree>
233+
`,
234+
})
235+
export class TreeExampleComponent {
236+
nodes: NgbTreeNode[] = [
237+
{
238+
id: 'parent',
239+
label: 'Parent',
240+
expanded: true,
241+
children: [
242+
{ id: 'child-1', label: 'Child 1' },
243+
{ id: 'child-2', label: 'Child 2' },
244+
],
245+
},
246+
];
247+
248+
onExpand(node: NgbTreeNode) {}
249+
onCollapse(node: NgbTreeNode) {}
250+
onSelection(selected: NgbTreeNode[]) {}
251+
}
252+
```
182253

183254
@Component({
184255
standalone: true,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "ngbootstrap",
2+
"name": "@angular-bootstrap/ngbootstrap",
33
"version": "0.0.1",
44
"description": "Angular UI library providing datagrid, drag-and-drop, pagination, and stepper components with Bootstrap-friendly styling.",
55
"author": {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export * from './datagrid';
33
export * from './drag-drop';
44
export * from './pagination';
55
export * from './stepper';
6+
export * from './splitter';
7+
export * from './typeahead';
8+
export * from './tree';

src/splitter/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './public-api';

src/splitter/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "ngbootstrap/splitter",
3+
"ngPackage": {
4+
"lib": {
5+
"entryFile": "index.ts"
6+
}
7+
}
8+
}

src/splitter/public-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './src/splitter/splitter.component';
2+
export * from './src/splitter/splitter.types';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core';
2+
3+
@Component({
4+
selector: 'ngb-splitter-pane',
5+
standalone: true,
6+
template: `<ng-template #paneTpl><ng-content></ng-content></ng-template>`,
7+
})
8+
export class NgbSplitterPaneComponent {
9+
@ViewChild('paneTpl', { static: true }) templateRef!: TemplateRef<any>;
10+
11+
@Input() size?: string;
12+
@Input() min?: string;
13+
@Input() max?: string;
14+
@Input() collapsible = false;
15+
@Input() collapsed = false;
16+
17+
@Output() collapsedChange = new EventEmitter<boolean>();
18+
19+
toggleCollapsed(next?: boolean) {
20+
const value = next ?? !this.collapsed;
21+
if (value !== this.collapsed) {
22+
this.collapsed = value;
23+
this.collapsedChange.emit(this.collapsed);
24+
}
25+
}
26+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Component, ViewChild } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
import { NgbSplitterComponent } from './splitter.component';
5+
import { NgbSplitterPaneComponent } from './splitter-pane.component';
6+
7+
@Component({
8+
standalone: true,
9+
imports: [NgbSplitterComponent, NgbSplitterPaneComponent],
10+
template: `
11+
<ngb-splitter [orientation]="orientation">
12+
<ngb-splitter-pane size="200px" min="120px" [collapsible]="true" (collapsedChange)="collapsedEvents.push($event)">
13+
<div>Pane A</div>
14+
</ngb-splitter-pane>
15+
<ngb-splitter-pane size="200px">
16+
<div>Pane B</div>
17+
</ngb-splitter-pane>
18+
</ngb-splitter>
19+
`,
20+
})
21+
class HostComponent {
22+
@ViewChild(NgbSplitterComponent) splitter!: NgbSplitterComponent;
23+
orientation: 'horizontal' | 'vertical' = 'horizontal';
24+
collapsedEvents: boolean[] = [];
25+
}
26+
27+
describe('NgbSplitterComponent', () => {
28+
let fixture: ComponentFixture<HostComponent>;
29+
let host: HostComponent;
30+
31+
beforeEach(async () => {
32+
await TestBed.configureTestingModule({
33+
imports: [HostComponent],
34+
}).compileComponents();
35+
36+
fixture = TestBed.createComponent(HostComponent);
37+
host = fixture.componentInstance;
38+
fixture.detectChanges();
39+
});
40+
41+
it('should render panes and handles', () => {
42+
const handles = fixture.debugElement.queryAll(By.css('button[role="separator"]'));
43+
expect(host.splitter).toBeTruthy();
44+
expect(handles.length).toBe(1);
45+
});
46+
47+
it('should toggle collapsible pane via handle', () => {
48+
const handle = fixture.debugElement.query(By.css('button[role="separator"]')).nativeElement as HTMLButtonElement;
49+
handle.dispatchEvent(new Event('dblclick'));
50+
fixture.detectChanges();
51+
expect(host.collapsedEvents[0]).toBe(true);
52+
});
53+
54+
it('should apply orientation classes', () => {
55+
const container = fixture.debugElement.query(By.css('ngb-splitter > div')).nativeElement as HTMLElement;
56+
expect(container.classList.contains('flex-row')).toBe(true);
57+
host.orientation = 'vertical';
58+
fixture.detectChanges();
59+
expect(container.classList.contains('flex-column')).toBe(true);
60+
});
61+
});

0 commit comments

Comments
 (0)