• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-webcomponents / 19039004852

03 Nov 2025 03:01PM UTC coverage: 98.187% (-0.005%) from 98.192%
19039004852

Pull #1941

github

web-flow
Merge 4cc20d9a7 into 23e8af1da
Pull Request #1941: Code cleanup for expansion panel and accordion components

5325 of 5603 branches covered (95.04%)

Branch coverage included in aggregate %.

167 of 167 new or added lines in 2 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

35358 of 35831 relevant lines covered (98.68%)

1605.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.92
/src/components/accordion/accordion.ts
1
import { html, LitElement } from 'lit';
10✔
2
import { property } from 'lit/decorators.js';
10✔
3
import {
10✔
4
  addKeybindings,
10✔
5
  altKey,
10✔
6
  arrowDown,
10✔
7
  arrowUp,
10✔
8
  endKey,
10✔
9
  homeKey,
10✔
10
  shiftKey,
10✔
11
} from '../common/controllers/key-bindings.js';
10✔
12
import { addSlotController, setSlots } from '../common/controllers/slot.js';
10✔
13
import { registerComponent } from '../common/definitions/register.js';
10✔
14
import { addSafeEventListener, first, last } from '../common/util.js';
10✔
15
import IgcExpansionPanelComponent from '../expansion-panel/expansion-panel.js';
10✔
16
import { styles } from './themes/accordion.base.css.js';
10✔
17

10✔
18
/**
10✔
19
 * The Accordion is a container-based component that can house multiple expansion panels
10✔
20
 * and offers keyboard navigation.
10✔
21
 *
10✔
22
 * @element igc-accordion
10✔
23
 *
10✔
24
 * @slot - Renders the expansion panels inside default slot.
10✔
25
 */
10✔
26
export default class IgcAccordionComponent extends LitElement {
10✔
27
  public static readonly tagName = 'igc-accordion';
10✔
28
  public static override styles = styles;
10✔
29

10✔
30
  /* blazorSuppress */
10✔
31
  public static register(): void {
10✔
32
    registerComponent(IgcAccordionComponent, IgcExpansionPanelComponent);
1✔
33
  }
1✔
34

10✔
35
  //#region Internal state and properties
10✔
36

10✔
37
  private _panels: IgcExpansionPanelComponent[] = [];
10✔
38

10✔
39
  private readonly _slots = addSlotController(this, {
10✔
40
    slots: setSlots(),
10✔
41
    onChange: this._handleSlotChange,
10✔
42
    initial: true,
10✔
43
  });
10✔
44

10✔
45
  private get _interactivePanels(): IgcExpansionPanelComponent[] {
10✔
46
    return this._panels.filter((panel) => !panel.disabled);
53✔
47
  }
53✔
48

10✔
49
  //#endregion
10✔
50

10✔
51
  //#region Public attributes and properties
10✔
52

10✔
53
  /**
10✔
54
   * Allows only one panel to be expanded at a time.
10✔
55
   * @attr single-expand
10✔
56
   * @default false
10✔
57
   */
10✔
58
  @property({ type: Boolean, reflect: true, attribute: 'single-expand' })
10✔
59
  public singleExpand = false;
10✔
60

10✔
61
  /* blazorSuppress */
10✔
62
  /** Returns all of the accordions's direct igc-expansion-panel children. */
10✔
63
  public get panels(): IgcExpansionPanelComponent[] {
10✔
64
    return Array.from(this._panels);
31✔
65
  }
31✔
66

10✔
67
  //#endregion
10✔
68

10✔
69
  constructor() {
10✔
70
    super();
25✔
71

25✔
72
    addSafeEventListener(this, 'igcOpening' as any, this._handlePanelOpening);
25✔
73

25✔
74
    addKeybindings(this, { skip: this._skipKeybinding })
25✔
75
      .set(homeKey, this._navigateToFirst)
25✔
76
      .set(endKey, this._navigateToLast)
25✔
77
      .set(arrowUp, this._navigateToPrevious)
25✔
78
      .set(arrowDown, this._navigateToNext)
25✔
79
      .set([shiftKey, altKey, arrowDown], this._expandAll)
25✔
80
      .set([shiftKey, altKey, arrowUp], this._collapseAll);
25✔
81
  }
25✔
82

10✔
83
  //#region Event handlers
10✔
84

10✔
85
  private _handleSlotChange(): void {
10✔
86
    this._panels = this._slots.getAssignedElements('[default]', {
50✔
87
      selector: IgcExpansionPanelComponent.tagName,
50✔
88
    });
50✔
89
  }
50✔
90

10✔
91
  private async _handlePanelOpening(event: Event): Promise<void> {
10✔
92
    const current = event.target as IgcExpansionPanelComponent;
12✔
93

12✔
94
    if (!(this.singleExpand && this.panels.includes(current))) {
12✔
95
      return;
8✔
96
    }
8✔
97

4✔
98
    await Promise.all(
4✔
99
      this._interactivePanels
4✔
100
        .filter((panel) => panel.open && panel !== current)
4✔
101
        .map((panel) => this._closePanel(panel))
4✔
102
    );
4✔
103
  }
12✔
104

10✔
105
  //#endregion
10✔
106

10✔
107
  //#region Keyboard interaction handlers
10✔
108

10✔
109
  private _skipKeybinding(target: Element): boolean {
10✔
110
    return !(
34✔
111
      target instanceof IgcExpansionPanelComponent &&
34✔
112
      this._interactivePanels.includes(target)
34✔
113
    );
34✔
114
  }
34✔
115

10✔
116
  private _navigateToFirst(): void {
10✔
117
    this._getPanelHeader(first(this._interactivePanels))?.focus();
1✔
118
  }
1✔
119

10✔
120
  private _navigateToLast(): void {
10✔
121
    this._getPanelHeader(last(this._interactivePanels))?.focus();
1✔
122
  }
1✔
123

10✔
124
  private _navigateToPrevious(event: KeyboardEvent): void {
10✔
125
    const current = event.target as IgcExpansionPanelComponent;
3✔
126
    const next = this._getNextPanel(current, -1);
3✔
127

3✔
128
    if (next !== current) {
3✔
129
      this._getPanelHeader(next)?.focus();
3✔
130
    }
3✔
131
  }
3✔
132

10✔
133
  private _navigateToNext(event: KeyboardEvent): void {
10✔
134
    const current = event.target as IgcExpansionPanelComponent;
5✔
135
    const next = this._getNextPanel(current, 1);
5✔
136

5✔
137
    if (next !== current) {
5✔
138
      this._getPanelHeader(next)?.focus();
4✔
139
    }
4✔
140
  }
5✔
141

10✔
142
  private async _collapseAll(): Promise<void> {
10✔
143
    await Promise.all(
2✔
144
      this._interactivePanels.map((panel) => this._closePanel(panel))
2✔
145
    );
2✔
146
  }
2✔
147

10✔
148
  private async _expandAll(event: KeyboardEvent): Promise<void> {
10✔
149
    const current = event.target as IgcExpansionPanelComponent;
3✔
150

3✔
151
    if (this.singleExpand) {
3✔
152
      const closePromises = this._interactivePanels
1✔
153
        .filter((panel) => panel.open && panel !== current)
1✔
154
        .map((panel) => this._closePanel(panel));
1✔
155

1✔
156
      await Promise.all(closePromises);
1✔
157
      await this._openPanel(current);
1✔
158
    } else {
3✔
159
      await Promise.all(
2✔
160
        this._interactivePanels.map((panel) => this._openPanel(panel))
2✔
161
      );
2✔
162
    }
2✔
163
  }
3✔
164

10✔
165
  //#endregion
10✔
166

10✔
167
  //#region Internal API
10✔
168

10✔
169
  private _getPanelHeader(
10✔
170
    panel: IgcExpansionPanelComponent
9✔
171
  ): HTMLElement | undefined {
9✔
172
    // biome-ignore lint/complexity/useLiteralKeys: Direct property access instead of DOM query
9✔
173
    return panel['_headerRef'].value;
9✔
174
  }
9✔
175

10✔
176
  private _getNextPanel(
10✔
177
    panel: IgcExpansionPanelComponent,
8✔
178
    dir: 1 | -1 = 1
8✔
179
  ): IgcExpansionPanelComponent {
8✔
180
    const panels = this._interactivePanels;
8✔
181
    const idx = panels.indexOf(panel);
8✔
182

8✔
183
    return panels[idx + dir] || panel;
8✔
184
  }
8✔
185

10✔
186
  private async _closePanel(p: IgcExpansionPanelComponent): Promise<void> {
10✔
187
    const args = { detail: p };
10✔
188

10✔
189
    if (!(p.open && p.emitEvent('igcClosing', { cancelable: true, ...args }))) {
10!
UNCOV
190
      return;
×
UNCOV
191
    }
×
192

10✔
193
    if (await p.hide()) {
10✔
194
      p.emitEvent('igcClosed', args);
8✔
195
    }
8✔
196
  }
10✔
197

10✔
198
  private async _openPanel(p: IgcExpansionPanelComponent): Promise<void> {
10✔
199
    const args = { detail: p };
6✔
200

6✔
201
    if (p.open || !p.emitEvent('igcOpening', { cancelable: true, ...args })) {
6✔
202
      return;
1✔
203
    }
1✔
204

5✔
205
    if (await p.show()) {
5✔
206
      p.emitEvent('igcOpened', args);
5✔
207
    }
5✔
208
  }
6✔
209

10✔
210
  //#endregion
10✔
211

10✔
212
  //#region Public API
10✔
213

10✔
214
  /** Hides all of the child expansion panels' contents. */
10✔
215
  public async hideAll(): Promise<void> {
10✔
216
    await Promise.all(this.panels.map((panel) => panel.hide()));
3✔
217
  }
3✔
218

10✔
219
  /** Shows all of the child expansion panels' contents. */
10✔
220
  public async showAll(): Promise<void> {
10✔
221
    await Promise.all(this.panels.map((panel) => panel.show()));
3✔
222
  }
3✔
223

10✔
224
  //#endregion
10✔
225

10✔
226
  protected override render() {
10✔
227
    return html`<slot></slot>`;
54✔
228
  }
54✔
229
}
10✔
230

10✔
231
declare global {
10✔
232
  interface HTMLElementTagNameMap {
10✔
233
    'igc-accordion': IgcAccordionComponent;
10✔
234
  }
10✔
235
}
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc