Skip to content

Commit 1ddbcaf

Browse files
feat(module:collapse): add nzCollapsible support (#9349)
feat(module:collapse): add nzCollapsible support --------- Co-authored-by: hyperlife1119 <hyperlife1119@qq.com>
1 parent b5c256d commit 1ddbcaf

6 files changed

Lines changed: 204 additions & 30 deletions

File tree

‎components/collapse/collapse-panel.component.ts‎

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
*/
55

66
import {
7+
booleanAttribute,
78
ChangeDetectionStrategy,
9+
ChangeDetectorRef,
810
Component,
11+
DestroyRef,
912
ElementRef,
1013
EventEmitter,
14+
inject,
1115
Input,
16+
NgZone,
1217
OnInit,
1318
Output,
1419
TemplateRef,
1520
ViewChild,
1621
ViewEncapsulation,
17-
booleanAttribute,
18-
inject,
19-
NgZone,
20-
ChangeDetectorRef,
21-
DestroyRef
22+
type AfterViewInit
2223
} from '@angular/core';
2324
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2425
import { filter } from 'rxjs/operators';
@@ -41,9 +42,16 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'collapsePanel';
4142
encapsulation: ViewEncapsulation.None,
4243
animations: [collapseMotion],
4344
template: `
44-
<div #collapseHeader role="button" [attr.aria-expanded]="nzActive" class="ant-collapse-header">
45+
<div
46+
#collapseHeader
47+
role="button"
48+
[attr.aria-expanded]="nzActive"
49+
class="ant-collapse-header"
50+
[class.ant-collapse-icon-collapsible-only]="nzCollapsible === 'icon'"
51+
[class.ant-collapse-header-collapsible-only]="nzCollapsible === 'header'"
52+
>
4553
@if (nzShowArrow) {
46-
<div>
54+
<div role="button" #collapseIcon class="ant-collapse-expand-icon">
4755
<ng-container *nzStringTemplateOutlet="nzExpandedIcon; let expandedIcon">
4856
<nz-icon [nzType]="expandedIcon || 'right'" class="ant-collapse-arrow" [nzRotate]="nzActive ? 90 : 0" />
4957
</ng-container>
@@ -73,11 +81,11 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'collapsePanel';
7381
class: 'ant-collapse-item',
7482
'[class.ant-collapse-no-arrow]': '!nzShowArrow',
7583
'[class.ant-collapse-item-active]': 'nzActive',
76-
'[class.ant-collapse-item-disabled]': 'nzDisabled'
84+
'[class.ant-collapse-item-disabled]': 'nzDisabled || nzCollapsible === "disabled"'
7785
},
7886
imports: [NzOutletModule, NzIconModule]
7987
})
80-
export class NzCollapsePanelComponent implements OnInit {
88+
export class NzCollapsePanelComponent implements OnInit, AfterViewInit {
8189
private ngZone = inject(NgZone);
8290
private cdr = inject(ChangeDetectorRef);
8391
private destroyRef = inject(DestroyRef);
@@ -87,14 +95,19 @@ export class NzCollapsePanelComponent implements OnInit {
8795
readonly _nzModuleName: NzConfigKey = NZ_CONFIG_MODULE_NAME;
8896

8997
@Input({ transform: booleanAttribute }) nzActive = false;
98+
/**
99+
* @deprecated Use `nzCollapsible` instead with the value `'disabled'`.
100+
*/
90101
@Input({ transform: booleanAttribute }) nzDisabled = false;
91102
@Input({ transform: booleanAttribute }) @WithConfig() nzShowArrow: boolean = true;
92103
@Input() nzExtra?: string | TemplateRef<void>;
93104
@Input() nzHeader?: string | TemplateRef<void>;
94105
@Input() nzExpandedIcon?: string | TemplateRef<void>;
106+
@Input() nzCollapsible?: 'disabled' | 'header' | 'icon';
95107
@Output() readonly nzActiveChange = new EventEmitter<boolean>();
96108

97-
@ViewChild('collapseHeader', { static: true }) collapseHeader!: ElementRef<HTMLElement>;
109+
@ViewChild('collapseHeader') collapseHeader!: ElementRef<HTMLElement>;
110+
@ViewChild('collapseIcon') collapseIcon?: ElementRef<HTMLElement>;
98111

99112
markForCheck(): void {
100113
this.cdr.markForCheck();
@@ -110,10 +123,16 @@ export class NzCollapsePanelComponent implements OnInit {
110123

111124
ngOnInit(): void {
112125
this.nzCollapseComponent.addPanel(this);
126+
}
113127

114-
fromEventOutsideAngular(this.collapseHeader.nativeElement, 'click')
128+
ngAfterViewInit(): void {
129+
let fromEvent$ = fromEventOutsideAngular(this.collapseHeader.nativeElement, 'click');
130+
if (this.nzShowArrow && this.nzCollapsible === 'icon' && this.collapseIcon) {
131+
fromEvent$ = fromEventOutsideAngular(this.collapseIcon!.nativeElement, 'click');
132+
}
133+
fromEvent$
115134
.pipe(
116-
filter(() => !this.nzDisabled),
135+
filter(() => !this.nzDisabled && this.nzCollapsible !== 'disabled'),
117136
takeUntilDestroyed(this.destroyRef)
118137
)
119138
.subscribe(() => {

‎components/collapse/collapse.spec.ts‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,86 @@ describe('collapse', () => {
178178
});
179179
});
180180

181+
describe('collapse collapsible', () => {
182+
let fixture: ComponentFixture<NzTestCollapseCollapsibleComponent>;
183+
let panel: DebugElement;
184+
185+
beforeEach(() => {
186+
fixture = TestBed.createComponent(NzTestCollapseCollapsibleComponent);
187+
fixture.detectChanges();
188+
panel = fixture.debugElement.query(By.directive(NzCollapsePanelComponent));
189+
});
190+
191+
it('should only toggle by icon when nzCollapsible is "icon"', () => {
192+
const headerEl = panel.nativeElement.querySelector('.ant-collapse-header') as HTMLElement;
193+
expect(headerEl.classList).toContain('ant-collapse-icon-collapsible-only');
194+
// initial state
195+
expect(panel.nativeElement.classList).not.toContain('ant-collapse-item-active');
196+
197+
// click header text should NOT toggle
198+
(panel.nativeElement.querySelector('.ant-collapse-header-text') as HTMLElement).click();
199+
fixture.detectChanges();
200+
expect(panel.nativeElement.classList).not.toContain('ant-collapse-item-active');
201+
202+
// click icon should toggle open
203+
(panel.nativeElement.querySelector('.ant-collapse-expand-icon') as HTMLElement).click();
204+
fixture.detectChanges();
205+
expect(panel.nativeElement.classList).toContain('ant-collapse-item-active');
206+
207+
// click header text again should NOT toggle
208+
(panel.nativeElement.querySelector('.ant-collapse-header-text') as HTMLElement).click();
209+
fixture.detectChanges();
210+
expect(panel.nativeElement.classList).toContain('ant-collapse-item-active');
211+
212+
// click icon should toggle close
213+
(panel.nativeElement.querySelector('.ant-collapse-expand-icon') as HTMLElement).click();
214+
fixture.detectChanges();
215+
expect(panel.nativeElement.classList).not.toContain('ant-collapse-item-active');
216+
});
217+
218+
it('should not toggle when nzCollapsible is "disabled"', () => {
219+
fixture.componentInstance.collapsible = 'disabled';
220+
fixture.detectChanges();
221+
222+
const header = panel.nativeElement.querySelector('.ant-collapse-header') as HTMLElement;
223+
header.click();
224+
fixture.detectChanges();
225+
expect(panel.nativeElement.classList).not.toContain('ant-collapse-item-active');
226+
227+
const icon = panel.nativeElement.querySelector('.ant-collapse-expand-icon') as HTMLElement;
228+
icon.click();
229+
fixture.detectChanges();
230+
expect(panel.nativeElement.classList).not.toContain('ant-collapse-item-active');
231+
});
232+
233+
it('should toggle by header when nzCollapsible is "header"', () => {
234+
// Recreate fixture and set mode BEFORE first detectChanges so listeners bind to header
235+
const localFixture = TestBed.createComponent(NzTestCollapseCollapsibleComponent);
236+
localFixture.componentInstance.collapsible = 'header';
237+
localFixture.detectChanges();
238+
const localPanel = localFixture.debugElement.query(By.directive(NzCollapsePanelComponent));
239+
240+
const header = localPanel.nativeElement.querySelector('.ant-collapse-header') as HTMLElement;
241+
expect(header.classList).toContain('ant-collapse-header-collapsible-only');
242+
expect(localPanel.nativeElement.classList).not.toContain('ant-collapse-item-active');
243+
244+
// click header toggles
245+
(localPanel.nativeElement.querySelector('.ant-collapse-header-text') as HTMLElement).click();
246+
localFixture.detectChanges();
247+
expect(localPanel.nativeElement.classList).toContain('ant-collapse-item-active');
248+
249+
// click header toggles again (close)
250+
(localPanel.nativeElement.querySelector('.ant-collapse-header-text') as HTMLElement).click();
251+
localFixture.detectChanges();
252+
expect(localPanel.nativeElement.classList).not.toContain('ant-collapse-item-active');
253+
254+
// clicking icon (which is inside header) should also toggle because header listens
255+
(localPanel.nativeElement.querySelector('.ant-collapse-expand-icon') as HTMLElement).click();
256+
localFixture.detectChanges();
257+
expect(localPanel.nativeElement.classList).toContain('ant-collapse-item-active');
258+
});
259+
});
260+
181261
describe('RTL', () => {
182262
it('should className correct on dir change', () => {
183263
const fixture = TestBed.createComponent(NzTestCollapseRtlComponent);
@@ -290,6 +370,27 @@ export class NzTestCollapseTemplateComponent {}
290370
})
291371
export class NzTestCollapseIconComponent {}
292372

373+
@Component({
374+
imports: [NzCollapseModule],
375+
template: `
376+
<nz-collapse>
377+
<nz-collapse-panel
378+
[(nzActive)]="active"
379+
[nzCollapsible]="collapsible"
380+
[nzShowArrow]="showArrow"
381+
nzHeader="Header"
382+
>
383+
<p>Content</p>
384+
</nz-collapse-panel>
385+
</nz-collapse>
386+
`
387+
})
388+
export class NzTestCollapseCollapsibleComponent {
389+
active = false;
390+
collapsible: 'disabled' | 'header' | 'icon' = 'icon';
391+
showArrow = true;
392+
}
393+
293394
@Component({
294395
imports: [BidiModule, NzTestCollapseBasicComponent],
295396
template: `
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
order: 8
3+
title:
4+
zh-CN: 可折叠
5+
en-US: Collapsible
6+
---
7+
8+
## zh-CN
9+
10+
通过 `nzCollapsible` 属性,可以设置面板的可折叠触发区域
11+
12+
## en-US
13+
14+
Specify the trigger area of collapsible by `nzCollapsible`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Component } from '@angular/core';
2+
3+
import { NzCollapseModule } from 'ng-zorro-antd/collapse';
4+
import { NzFlexModule } from 'ng-zorro-antd/flex';
5+
6+
@Component({
7+
selector: 'nz-demo-collapse-collapsible',
8+
imports: [NzCollapseModule, NzFlexModule],
9+
template: `
10+
<div nz-flex nzVertical nzWrap="wrap" [nzGap]="16">
11+
<nz-collapse>
12+
<nz-collapse-panel nzHeader="This is panel with header collapsible" nzCollapsible="header">
13+
<p style="margin:0;">
14+
A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome
15+
guest in many households across the world.
16+
</p>
17+
</nz-collapse-panel>
18+
</nz-collapse>
19+
<nz-collapse>
20+
<nz-collapse-panel nzHeader="This is panel with icon collapsible" nzCollapsible="icon">
21+
<p style="margin:0;">
22+
A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome
23+
guest in many households across the world.
24+
</p>
25+
</nz-collapse-panel>
26+
</nz-collapse>
27+
<nz-collapse>
28+
<nz-collapse-panel nzHeader="This is panel with disabled collapsible" nzCollapsible="disabled">
29+
<p style="margin:0;">
30+
A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome
31+
guest in many households across the world.
32+
</p>
33+
</nz-collapse-panel>
34+
</nz-collapse>
35+
</div>
36+
`
37+
})
38+
export class NzDemoCollapseCollapsibleComponent {}

‎components/collapse/doc/index.en-US.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ description: A content area which can be collapsed and expanded.
2626

2727
### nz-collapse-panel
2828

29-
| Property | Description | Type | Default | Global Config |
30-
| ------------------ | ------------------------------------------- | ----------------------------- | ------- | ------------- |
31-
| `[nzDisabled]` | If `true`, panel cannot be opened or closed | `boolean` | `false` | - |
32-
| `[nzHeader]` | Title of the panel | `string \| TemplateRef<void>` | - | - |
33-
| `[nzExpandedIcon]` | Customize an icon for toggle | `string \| TemplateRef<void>` | - | - |
34-
| `[nzExtra]` | Extra element in the corner | `string \| TemplateRef<void>` | - | - |
35-
| `[nzShowArrow]` | Display arrow or not | `boolean` | `true` | ✅ |
36-
| `[nzActive]` | Active status of panel, double binding | `boolean` | - | - |
37-
| `(nzActiveChange)` | Callback function of the active status | `EventEmitter<boolean>` | - | - |
29+
| Property | Description | Type | Default | Global Config |
30+
| ------------------ | ------------------------------------------- | ---------------------------------- | ------- | ------------- |
31+
| `[nzDisabled]` | If `true`, panel cannot be opened or closed | `boolean` | `false` | - |
32+
| `[nzHeader]` | Title of the panel | `string \| TemplateRef<void>` | - | - |
33+
| `[nzExpandedIcon]` | Customize an icon for toggle | `string \| TemplateRef<void>` | - | - |
34+
| `[nzExtra]` | Extra element in the corner | `string \| TemplateRef<void>` | - | - |
35+
| `[nzShowArrow]` | Display arrow or not | `boolean` | `true` | ✅ |
36+
| `[nzActive]` | Active status of panel, double binding | `boolean` | - | - |
37+
| `[nzCollapsible]` | Set collapsible trigger area | `'header' \| 'icon' \| 'disabled'` | - | - |
38+
| `(nzActiveChange)` | Callback function of the active status | `EventEmitter<boolean>` | - | - |

‎components/collapse/doc/index.zh-CN.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ description: 可以折叠/展开的内容区域。
2727

2828
### nz-collapse-panel
2929

30-
| 参数 | 说明 | 类型 | 默认值 | 全局配置 |
31-
| ------------------ | ------------------------------------------ | ----------------------------- | ------- | -------- |
32-
| `[nzDisabled]` | 禁用后的面板展开与否将无法通过用户交互改变 | `boolean` | `false` | - |
33-
| `[nzHeader]` | 面板头内容 | `string \| TemplateRef<void>` | - | - |
34-
| `[nzExpandedIcon]` | 自定义切换图标 | `string \| TemplateRef<void>` | - | - |
35-
| `[nzExtra]` | 自定义渲染每个面板右上角的内容 | `string \| TemplateRef<void>` | - | - |
36-
| `[nzShowArrow]` | 是否展示箭头 | `boolean` | `true` | ✅ |
37-
| `[nzActive]` | 面板是否展开,可双向绑定 | `boolean` | - | - |
38-
| `(nzActiveChange)` | 面板展开回调 | `EventEmitter<boolean>` | - | - |
30+
| 参数 | 说明 | 类型 | 默认值 | 全局配置 |
31+
| ------------------ | ------------------------------------------ | ---------------------------------- | ------- | -------- |
32+
| `[nzDisabled]` | 禁用后的面板展开与否将无法通过用户交互改变 | `boolean` | `false` | - |
33+
| `[nzHeader]` | 面板头内容 | `string \| TemplateRef<void>` | - | - |
34+
| `[nzExpandedIcon]` | 自定义切换图标 | `string \| TemplateRef<void>` | - | - |
35+
| `[nzExtra]` | 自定义渲染每个面板右上角的内容 | `string \| TemplateRef<void>` | - | - |
36+
| `[nzShowArrow]` | 是否展示箭头 | `boolean` | `true` | ✅ |
37+
| `[nzActive]` | 面板是否展开,可双向绑定 | `boolean` | - | - |
38+
| `[nzCollapsible]` | 设置可折叠触发区域 | `'header' \| 'icon' \| 'disabled'` | - | - |
39+
| `(nzActiveChange)` | 面板展开回调 | `EventEmitter<boolean>` | - | - |

0 commit comments

Comments
 (0)