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

IgniteUI / igniteui-webcomponents / 16285572060

15 Jul 2025 06:12AM UTC coverage: 98.28% (+0.02%) from 98.265%
16285572060

Pull #1767

github

web-flow
Merge b0f28d031 into f3d93445d
Pull Request #1767: feat: Slot controller

4943 of 5194 branches covered (95.17%)

Branch coverage included in aggregate %.

1233 of 1243 new or added lines in 23 files covered. (99.2%)

3 existing lines in 1 file now uncovered.

31976 of 32371 relevant lines covered (98.78%)

1741.54 hits per line

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

99.29
/src/components/select/select.ts
1
import { html, type TemplateResult } from 'lit';
10✔
2
import { property, query, state } from 'lit/decorators.js';
10✔
3
import { ifDefined } from 'lit/directives/if-defined.js';
10✔
4
import { addThemingController } from '../../theming/theming-controller.js';
10✔
5
import {
10✔
6
  addKeybindings,
10✔
7
  altKey,
10✔
8
  arrowDown,
10✔
9
  arrowLeft,
10✔
10
  arrowRight,
10✔
11
  arrowUp,
10✔
12
  endKey,
10✔
13
  enterKey,
10✔
14
  escapeKey,
10✔
15
  homeKey,
10✔
16
  spaceBar,
10✔
17
  tabKey,
10✔
18
} from '../common/controllers/key-bindings.js';
10✔
19
import { addRootClickController } from '../common/controllers/root-click.js';
10✔
20
import { addRootScrollHandler } from '../common/controllers/root-scroll.js';
10✔
21
import { addSlotController, setSlots } from '../common/controllers/slot.js';
10✔
22
import { blazorAdditionalDependencies } from '../common/decorators/blazorAdditionalDependencies.js';
10✔
23
import { watch } from '../common/decorators/watch.js';
10✔
24
import { registerComponent } from '../common/definitions/register.js';
10✔
25
import {
10✔
26
  getActiveItems,
10✔
27
  getItems,
10✔
28
  getNextActiveItem,
10✔
29
  getPreviousActiveItem,
10✔
30
  IgcBaseComboBoxLikeComponent,
10✔
31
  setInitialSelectionState,
10✔
32
} from '../common/mixins/combo-box.js';
10✔
33
import type { AbstractConstructor } from '../common/mixins/constructor.js';
10✔
34
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
10✔
35
import { FormAssociatedRequiredMixin } from '../common/mixins/forms/associated-required.js';
10✔
36
import {
10✔
37
  createFormValueState,
10✔
38
  type FormValueOf,
10✔
39
} from '../common/mixins/forms/form-value.js';
10✔
40
import { partMap } from '../common/part-map.js';
10✔
41
import {
10✔
42
  addSafeEventListener,
10✔
43
  findElementFromEventPath,
10✔
44
  isString,
10✔
45
} from '../common/util.js';
10✔
46
import IgcIconComponent from '../icon/icon.js';
10✔
47
import IgcInputComponent from '../input/input.js';
10✔
48
import IgcPopoverComponent, {
10✔
49
  type PopoverPlacement,
10✔
50
} from '../popover/popover.js';
10✔
51
import type { PopoverScrollStrategy } from '../types.js';
10✔
52
import IgcValidationContainerComponent from '../validation-container/validation-container.js';
10✔
53
import IgcSelectGroupComponent from './select-group.js';
10✔
54
import IgcSelectHeaderComponent from './select-header.js';
10✔
55
import IgcSelectItemComponent from './select-item.js';
10✔
56
import { styles } from './themes/select.base.css.js';
10✔
57
import { styles as shared } from './themes/shared/select.common.css.js';
10✔
58
import { all } from './themes/themes.js';
10✔
59
import { selectValidators } from './validators.js';
10✔
60

10✔
61
export interface IgcSelectComponentEventMap {
10✔
62
  igcChange: CustomEvent<IgcSelectItemComponent>;
10✔
63
  // For analyzer meta only:
10✔
64
  /* skipWCPrefix */
10✔
65
  focus: FocusEvent;
10✔
66
  /* skipWCPrefix */
10✔
67
  blur: FocusEvent;
10✔
68
  igcOpening: CustomEvent<void>;
10✔
69
  igcOpened: CustomEvent<void>;
10✔
70
  igcClosing: CustomEvent<void>;
10✔
71
  igcClosed: CustomEvent<void>;
10✔
72
}
10✔
73

10✔
74
const Slots = setSlots(
10✔
75
  'prefix',
10✔
76
  'suffix',
10✔
77
  'header',
10✔
78
  'footer',
10✔
79
  'helper-text',
10✔
80
  'toggle-icon',
10✔
81
  'toggle-icon-expanded',
10✔
82
  'value-missing',
10✔
83
  'custom-error',
10✔
84
  'invalid'
10✔
85
);
10✔
86

10✔
87
/**
10✔
88
 * Represents a control that provides a menu of options.
10✔
89
 *
10✔
90
 * @element igc-select
10✔
91
 *
10✔
92
 * @slot - Renders the list of select items.
10✔
93
 * @slot prefix - Renders content before the input.
10✔
94
 * @slot suffix - Renders content after input.
10✔
95
 * @slot header - Renders a container before the list of options.
10✔
96
 * @slot footer - Renders a container after the list of options.
10✔
97
 * @slot helper-text - Renders content below the input.
10✔
98
 * @slot toggle-icon - Renders content inside the suffix container.
10✔
99
 * @slot toggle-icon-expanded - Renders content for the toggle icon when the component is in open state.
10✔
100
 * @slot value-missing - Renders content when the required validation fails.
10✔
101
 * @slot custom-error - Renders content when setCustomValidity(message) is set.
10✔
102
 * @slot invalid - Renders content when the component is in invalid state (validity.valid = false).
10✔
103
 *
10✔
104
 * @fires igcChange - Emitted when the control's checked state changes.
10✔
105
 * @fires igcOpening - Emitted just before the list of options is opened.
10✔
106
 * @fires igcOpened - Emitted after the list of options is opened.
10✔
107
 * @fires igcClosing - Emitter just before the list of options is closed.
10✔
108
 * @fires igcClosed - Emitted after the list of options is closed.
10✔
109
 *
10✔
110
 * @csspart list - The list wrapping container for the items of the igc-select.
10✔
111
 * @csspart input - The encapsulated igc-input of the igc-select.
10✔
112
 * @csspart label - The encapsulated text label of the igc-select.
10✔
113
 * @csspart prefix - The prefix wrapper of the input of the igc-select.
10✔
114
 * @csspart suffix - The suffix wrapper of the input of the igc-select.
10✔
115
 * @csspart toggle-icon - The toggle icon wrapper of the igc-select.
10✔
116
 * @csspart helper-text - The helper text wrapper of the igc-select.
10✔
117
 */
10✔
118
@blazorAdditionalDependencies(
10✔
119
  'IgcIconComponent, IgcInputComponent, IgcSelectGroupComponent, IgcSelectHeaderComponent, IgcSelectItemComponent'
10✔
120
)
10✔
121
export default class IgcSelectComponent extends FormAssociatedRequiredMixin(
10✔
122
  EventEmitterMixin<
10✔
123
    IgcSelectComponentEventMap,
10✔
124
    AbstractConstructor<IgcBaseComboBoxLikeComponent>
10✔
125
  >(IgcBaseComboBoxLikeComponent)
10✔
126
) {
10✔
127
  public static readonly tagName = 'igc-select';
10✔
128
  public static styles = [styles, shared];
10✔
129

10✔
130
  /* blazorSuppress */
10✔
131
  public static register(): void {
10✔
132
    registerComponent(
10✔
133
      IgcSelectComponent,
10✔
134
      IgcIconComponent,
10✔
135
      IgcInputComponent,
10✔
136
      IgcPopoverComponent,
10✔
137
      IgcSelectGroupComponent,
10✔
138
      IgcSelectHeaderComponent,
10✔
139
      IgcSelectItemComponent
10✔
140
    );
10✔
141
  }
10✔
142

10✔
143
  //#region Internal state
10✔
144

10✔
145
  protected override get __validators() {
10✔
146
    return selectValidators;
260✔
147
  }
260✔
148

10✔
149
  private _searchTerm = '';
10✔
150
  private _lastKeyTime = 0;
10✔
151

10✔
152
  private readonly _slots = addSlotController(this, { slots: Slots });
10✔
153

10✔
154
  private readonly _rootScrollController = addRootScrollHandler(this, {
10✔
155
    hideCallback: this._handleClosing,
10✔
156
  });
10✔
157

10✔
158
  protected override readonly _rootClickController = addRootClickController(
10✔
159
    this,
10✔
160
    {
10✔
161
      onHide: this._handleClosing,
10✔
162
    }
10✔
163
  );
10✔
164

10✔
165
  protected override readonly _formValue: FormValueOf<string | undefined> =
10✔
166
    createFormValueState<string | undefined>(this, {
10✔
167
      initialValue: undefined,
10✔
168
      transformers: {
10✔
169
        setValue: (value) => value || undefined,
10✔
170
        setDefaultValue: (value) => value || undefined,
10✔
171
      },
10✔
172
    });
10✔
173

10✔
174
  @state()
10✔
175
  protected _selectedItem: IgcSelectItemComponent | null = null;
10✔
176

10✔
177
  @state()
10✔
178
  protected _activeItem!: IgcSelectItemComponent;
10✔
179

10✔
180
  @query(IgcInputComponent.tagName, true)
10✔
181
  protected _input!: IgcInputComponent;
10✔
182

10✔
183
  protected get _activeItems(): IgcSelectItemComponent[] {
10✔
184
    return Array.from(
31✔
185
      getActiveItems<IgcSelectItemComponent>(
31✔
186
        this,
31✔
187
        IgcSelectItemComponent.tagName
31✔
188
      )
31✔
189
    );
31✔
190
  }
31✔
191

10✔
192
  //#endregion
10✔
193

10✔
194
  //#region Public attributes and properties
10✔
195

10✔
196
  /* @tsTwoWayProperty(true, "igcChange", "detail.value", false) */
10✔
197
  /**
10✔
198
   * The value attribute of the control.
10✔
199
   * @attr
10✔
200
   */
10✔
201
  @property()
10✔
202
  public set value(value: string | undefined) {
10✔
203
    this._updateValue(value);
31✔
204
    const item = this._getItem(this._formValue.value!);
31✔
205
    item ? this._setSelectedItem(item) : this._clearSelectedItem();
31✔
206
  }
31✔
207

10✔
208
  public get value(): string | undefined {
10✔
209
    return this._formValue.value;
841✔
210
  }
841✔
211

10✔
212
  /**
10✔
213
   * The outlined attribute of the control.
10✔
214
   * @attr
10✔
215
   */
10✔
216
  @property({ reflect: true, type: Boolean })
10✔
217
  public outlined = false;
10✔
218

10✔
219
  /**
10✔
220
   * The autofocus attribute of the control.
10✔
221
   * @attr
10✔
222
   */
10✔
223
  @property({ type: Boolean })
10✔
224
  public override autofocus!: boolean;
10✔
225

10✔
226
  /**
10✔
227
   * The distance of the select dropdown from its input.
10✔
228
   * @attr
10✔
229
   */
10✔
230
  @property({ type: Number })
10✔
231
  public distance = 0;
10✔
232

10✔
233
  /**
10✔
234
   * The label attribute of the control.
10✔
235
   * @attr
10✔
236
   */
10✔
237
  @property()
10✔
238
  public label!: string;
10✔
239

10✔
240
  /**
10✔
241
   * The placeholder attribute of the control.
10✔
242
   * @attr
10✔
243
   */
10✔
244
  @property()
10✔
245
  public placeholder!: string;
10✔
246

10✔
247
  /** The preferred placement of the select dropdown around its input.
10✔
248
   * @attr
10✔
249
   */
10✔
250
  @property()
10✔
251
  public placement: PopoverPlacement = 'bottom-start';
10✔
252

10✔
253
  /**
10✔
254
   * Determines the behavior of the component during scrolling of the parent container.
10✔
255
   * @attr scroll-strategy
10✔
256
   */
10✔
257
  @property({ attribute: 'scroll-strategy' })
10✔
258
  public scrollStrategy: PopoverScrollStrategy = 'scroll';
10✔
259

10✔
260
  /** Returns the items of the igc-select component. */
10✔
261
  public get items(): IgcSelectItemComponent[] {
10✔
262
    return Array.from(
259✔
263
      getItems<IgcSelectItemComponent>(this, IgcSelectItemComponent.tagName)
259✔
264
    );
259✔
265
  }
259✔
266

10✔
267
  /** Returns the groups of the igc-select component. */
10✔
268
  public get groups(): IgcSelectGroupComponent[] {
10✔
269
    return Array.from(
4✔
270
      getItems<IgcSelectGroupComponent>(this, IgcSelectGroupComponent.tagName)
4✔
271
    );
4✔
272
  }
4✔
273

10✔
274
  /** Returns the selected item from the dropdown or null.  */
10✔
275
  public get selectedItem(): IgcSelectItemComponent | null {
10✔
276
    return this._selectedItem;
349✔
277
  }
349✔
278

10✔
279
  //#endregion
10✔
280

10✔
281
  //#region Life-cycle hooks and watchers
10✔
282

10✔
283
  @watch('scrollStrategy', { waitUntilFirstUpdate: true })
10✔
284
  protected _scrollStrategyChange(): void {
10✔
285
    this._rootScrollController.update({ resetListeners: true });
2✔
286
  }
2✔
287

10✔
288
  @watch('open', { waitUntilFirstUpdate: true })
10✔
289
  protected _openChange(): void {
10✔
290
    this._rootClickController.update();
52✔
291
    this._rootScrollController.update();
52✔
292
  }
52✔
293

10✔
294
  constructor() {
10✔
295
    super();
83✔
296

83✔
297
    addThemingController(this, all);
83✔
298

83✔
299
    addKeybindings(this, {
83✔
300
      skip: () => this.disabled,
83✔
301
      bindingDefaults: { preventDefault: true, triggers: ['keydownRepeat'] },
83✔
302
    })
83✔
303
      .set([altKey, arrowDown], this._handleAltArrowDown)
83✔
304
      .set([altKey, arrowUp], this._handleAltArrowUp)
83✔
305
      .set(arrowDown, this._handleArrowDown)
83✔
306
      .set(arrowUp, this._handleArrowUp)
83✔
307
      .set(arrowLeft, this._handleArrowUp)
83✔
308
      .set(arrowRight, this._handleArrowDown)
83✔
309
      .set(tabKey, this._handleTab, { preventDefault: false })
83✔
310
      .set(escapeKey, this._handleEscape)
83✔
311
      .set(homeKey, this._handleHome)
83✔
312
      .set(endKey, this._handleEnd)
83✔
313
      .set(spaceBar, this._handleSpace)
83✔
314
      .set(enterKey, this._handleEnter);
83✔
315

83✔
316
    addSafeEventListener(this, 'keydown', this._handleSearch);
83✔
317
    addSafeEventListener(this, 'focusin', this._handleFocusIn);
83✔
318
    addSafeEventListener(this, 'focusout', this._handleFocusOut);
83✔
319
  }
83✔
320

10✔
321
  protected override async firstUpdated(): Promise<void> {
10✔
322
    await this.updateComplete;
83✔
323
    const selected = setInitialSelectionState(this.items);
83✔
324

83✔
325
    if (this.value && !selected) {
83✔
326
      this._selectItem(this._getItem(this.value), false);
1✔
327
    }
1✔
328

83✔
329
    if (selected && selected.value !== this.value) {
83✔
330
      this.defaultValue = selected.value;
3✔
331
      this._selectItem(selected, false);
3✔
332
    }
3✔
333

83✔
334
    if (this.autofocus) {
83✔
335
      this.focus();
1✔
336
    }
1✔
337

83✔
338
    this._updateValidity();
83✔
339
  }
83✔
340

10✔
341
  //#endregion
10✔
342

10✔
343
  //#region Keyboard event handlers
10✔
344

10✔
345
  private _handleSearch(event: KeyboardEvent): void {
10✔
346
    if (!/^.$/u.test(event.key)) {
94✔
347
      return;
72✔
348
    }
72✔
349

22✔
350
    event.preventDefault();
22✔
351
    const now = Date.now();
22✔
352

22✔
353
    if (now - this._lastKeyTime > 500) {
94✔
354
      this._searchTerm = '';
6✔
355
    }
6✔
356

22✔
357
    this._lastKeyTime = now;
22✔
358
    this._searchTerm += event.key.toLocaleLowerCase();
22✔
359

22✔
360
    const item = this._activeItems.find((item) =>
22✔
361
      item.textContent?.trim().toLocaleLowerCase().startsWith(this._searchTerm)
80✔
362
    );
22✔
363

22✔
364
    if (item) {
94✔
365
      this.open ? this._activateItem(item) : this._selectItem(item);
10✔
366
      this._activeItem.focus();
10✔
367
    }
10✔
368
  }
94✔
369

10✔
370
  private _handleEnter(): void {
10✔
371
    this.open && this._activeItem
5✔
372
      ? this._selectItem(this._activeItem)
3✔
373
      : this.handleAnchorClick();
2✔
374
  }
5✔
375

10✔
376
  private _handleSpace(): void {
10✔
377
    if (!this.open) {
2✔
378
      this.handleAnchorClick();
1✔
379
    }
1✔
380
  }
2✔
381

10✔
382
  private _handleArrowDown(): void {
10✔
383
    const item = getNextActiveItem(this.items, this._activeItem);
45✔
384
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
45✔
385
  }
45✔
386

10✔
387
  private _handleArrowUp(): void {
10✔
388
    const item = getPreviousActiveItem(this.items, this._activeItem);
11✔
389
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
11✔
390
  }
11✔
391

10✔
392
  private _handleAltArrowDown(): void {
10✔
393
    if (!this.open) {
1✔
394
      this._show(true);
1✔
395
      this._focusItemOnOpen();
1✔
396
    }
1✔
397
  }
1✔
398

10✔
399
  private async _handleAltArrowUp(): Promise<void> {
10✔
400
    if (this.open && (await this._hide(true))) {
1✔
401
      this._input.focus();
1✔
402
    }
1✔
403
  }
1✔
404

10✔
405
  private async _handleEscape(): Promise<void> {
10✔
406
    if (await this._hide(true)) {
2✔
407
      this._input.focus();
1✔
408
    }
1✔
409
  }
2✔
410

10✔
411
  private _handleTab(event: KeyboardEvent): void {
10✔
412
    if (this.open) {
3✔
413
      event.preventDefault();
3✔
414
      this._selectItem(this._activeItem ?? this._selectedItem);
3✔
415
      this._hide(true);
3✔
416
    }
3✔
417
  }
3✔
418

10✔
419
  private _handleHome(): void {
10✔
420
    const item = this._activeItems.at(0);
2✔
421
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
2✔
422
  }
2✔
423

10✔
424
  private _handleEnd(): void {
10✔
425
    const item = this._activeItems.at(-1);
2✔
426
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
2✔
427
  }
2✔
428

10✔
429
  //#endregion
10✔
430

10✔
431
  //#region Event listeners
10✔
432

10✔
433
  private _handleFocusIn({ relatedTarget }: FocusEvent): void {
10✔
434
    this._dirty = true;
56✔
435

56✔
436
    if (this.contains(relatedTarget as Node) || this.open) {
56✔
437
      return;
51✔
438
    }
51✔
439
  }
56✔
440

10✔
441
  private _handleFocusOut({ relatedTarget }: FocusEvent): void {
10✔
442
    if (this.contains(relatedTarget as Node)) {
56✔
443
      return;
34✔
444
    }
34✔
445

22✔
446
    this.checkValidity();
22✔
447
  }
56✔
448

10✔
449
  private _handleClick(event: PointerEvent): void {
10✔
450
    const item = findElementFromEventPath<IgcSelectItemComponent>(
9✔
451
      IgcSelectItemComponent.tagName,
9✔
452
      event
9✔
453
    );
9✔
454
    if (item && this._activeItems.includes(item)) {
9✔
455
      this._selectItem(item);
4✔
456
    }
4✔
457
  }
9✔
458

10✔
459
  private _handleChange(item: IgcSelectItemComponent): boolean {
10✔
460
    return this.emitEvent('igcChange', { detail: item });
29✔
461
  }
29✔
462

10✔
463
  private _handleClosing(): void {
10✔
464
    this._hide(true);
3✔
465
  }
3✔
466

10✔
467
  protected override handleAnchorClick(): void {
10✔
468
    super.handleAnchorClick();
7✔
469
    this._focusItemOnOpen();
7✔
470
  }
7✔
471

10✔
472
  //#endregion
10✔
473

10✔
474
  //#region Internal API
10✔
475

10✔
476
  private _activateItem(item: IgcSelectItemComponent): void {
10✔
477
    if (this._activeItem) {
82✔
478
      this._activeItem.active = false;
53✔
479
    }
53✔
480

82✔
481
    this._activeItem = item;
82✔
482
    this._activeItem.active = true;
82✔
483
  }
82✔
484

10✔
485
  private _setSelectedItem(
10✔
486
    item: IgcSelectItemComponent
56✔
487
  ): IgcSelectItemComponent {
56✔
488
    if (this._selectedItem) {
56✔
489
      this._selectedItem.selected = false;
16✔
490
    }
16✔
491

56✔
492
    this._selectedItem = item;
56✔
493
    this._selectedItem.selected = true;
56✔
494

56✔
495
    return this._selectedItem;
56✔
496
  }
56✔
497

10✔
498
  private _selectItem(
10✔
499
    item?: IgcSelectItemComponent,
45✔
500
    emit = true
45✔
501
  ): IgcSelectItemComponent | null {
45✔
502
    if (!item) {
45!
NEW
503
      this._clearSelectedItem();
×
504
      this._updateValue();
×
505
      return null;
×
506
    }
×
507

45✔
508
    const shouldFocus = emit && this.open;
45✔
509
    const shouldHide = emit && !this.keepOpenOnSelect;
45✔
510

45✔
511
    if (this._selectedItem === item) {
45✔
512
      if (shouldFocus) this._input.focus();
10✔
513
      return this._selectedItem;
10✔
514
    }
10✔
515

35✔
516
    const newItem = this._setSelectedItem(item);
35✔
517
    this._activateItem(newItem);
35✔
518
    this._updateValue(newItem.value);
35✔
519

35✔
520
    if (emit) this._handleChange(newItem);
45✔
521
    if (shouldFocus) this._input.focus();
45✔
522
    if (shouldHide) this._hide(true);
45✔
523

35✔
524
    return this._selectedItem;
35✔
525
  }
45✔
526

10✔
527
  private _navigateToActiveItem(item?: IgcSelectItemComponent): void {
10✔
528
    if (item) {
44✔
529
      this._activateItem(item);
44✔
530
      this._activeItem.focus({ preventScroll: true });
44✔
531
      item.scrollIntoView({ behavior: 'auto', block: 'nearest' });
44✔
532
    }
44✔
533
  }
44✔
534

10✔
535
  private _updateValue(value?: string): void {
10✔
536
    this._formValue.setValueAndFormState(value!);
69✔
537
    this._validate();
69✔
538
  }
69✔
539

10✔
540
  private _clearSelectedItem(): void {
10✔
541
    if (this._selectedItem) {
13✔
542
      this._selectedItem.selected = false;
5✔
543
    }
5✔
544
    this._selectedItem = null;
13✔
545
  }
13✔
546

10✔
547
  private async _focusItemOnOpen(): Promise<void> {
10✔
548
    await this.updateComplete;
8✔
549
    (this._selectedItem || this._activeItem)?.focus();
8!
550
  }
8✔
551

10✔
552
  private _getItem(value: string): IgcSelectItemComponent | undefined {
10✔
553
    return this.items.find((item) => item.value === value);
41✔
554
  }
41✔
555

10✔
556
  //#endregion
10✔
557

10✔
558
  //#region Public API
10✔
559

10✔
560
  /* alternateName: focusComponent */
10✔
561
  /** Sets focus on the component. */
10✔
562
  public override focus(options?: FocusOptions): void {
10✔
563
    this._input.focus(options);
3✔
564
  }
3✔
565

10✔
566
  /* alternateName: blurComponent */
10✔
567
  /** Removes focus from the component. */
10✔
568
  public override blur(): void {
10✔
569
    this._input.blur();
1✔
570
  }
1✔
571

10✔
572
  /** Checks the validity of the control and moves the focus to it if it is not valid. */
10✔
573
  public override reportValidity(): boolean {
10✔
574
    const valid = super.reportValidity();
4✔
575
    if (!valid) this._input.focus();
4✔
576
    return valid;
4✔
577
  }
4✔
578

10✔
579
  /* blazorSuppress */
10✔
580
  /** Navigates to the item with the specified value. If it exists, returns the found item, otherwise - null. */
10✔
581
  public navigateTo(value: string): IgcSelectItemComponent | null;
10✔
582
  /* blazorSuppress */
10✔
583
  /** Navigates to the item at the specified index. If it exists, returns the found item, otherwise - null. */
10✔
584
  public navigateTo(index: number): IgcSelectItemComponent | null;
10✔
585
  /* blazorSuppress */
10✔
586
  /** Navigates to the specified item. If it exists, returns the found item, otherwise - null. */
10✔
587
  public navigateTo(value: string | number): IgcSelectItemComponent | null {
10✔
588
    const item = isString(value) ? this._getItem(value) : this.items[value];
7✔
589

7✔
590
    if (item) {
7✔
591
      this._navigateToActiveItem(item);
4✔
592
    }
4✔
593

7✔
594
    return item ?? null;
7✔
595
  }
7✔
596

10✔
597
  /* blazorSuppress */
10✔
598
  /** Selects the item with the specified value. If it exists, returns the found item, otherwise - null. */
10✔
599
  public select(value: string): IgcSelectItemComponent | null;
10✔
600
  /* blazorSuppress */
10✔
601
  /** Selects the item at the specified index. If it exists, returns the found item, otherwise - null. */
10✔
602
  public select(index: number): IgcSelectItemComponent | null;
10✔
603
  /* blazorSuppress */
10✔
604
  /** Selects the specified item. If it exists, returns the found item, otherwise - null. */
10✔
605
  public select(value: string | number): IgcSelectItemComponent | null {
10✔
606
    const item = isString(value) ? this._getItem(value) : this.items[value];
8✔
607
    return item ? this._selectItem(item, false) : null;
8✔
608
  }
8✔
609

10✔
610
  /**  Resets the current value and selection of the component. */
10✔
611
  public clearSelection(): void {
10✔
612
    this._updateValue();
3✔
613
    this._clearSelectedItem();
3✔
614
  }
3✔
615

10✔
616
  //#endregion
10✔
617

10✔
618
  protected _renderInputSlots() {
10✔
619
    const prefix = this._slots.hasAssignedElements('prefix') ? 'prefix' : '';
281✔
620
    const suffix = this._slots.hasAssignedElements('suffix') ? 'suffix' : '';
281✔
621

281✔
622
    return html`
281✔
623
      <span slot=${prefix}>
281✔
624
        <slot name="prefix"></slot>
281✔
625
      </span>
281✔
626

281✔
627
      <span slot=${suffix}>
281✔
628
        <slot name="suffix"></slot>
281✔
629
      </span>
281✔
630
    `;
281✔
631
  }
281✔
632

10✔
633
  protected _renderToggleIcon() {
10✔
634
    const parts = { 'toggle-icon': true, filled: !!this.value };
281✔
635
    const iconName = this.open ? 'input_collapse' : 'input_expand';
281✔
636
    const iconHidden =
281✔
637
      this.open && this._slots.hasAssignedElements('toggle-icon-expanded');
281✔
638

281✔
639
    return html`
281✔
640
      <span slot="suffix" part=${partMap(parts)} aria-hidden="true">
281✔
641
        <slot name="toggle-icon" ?hidden=${iconHidden}>
281✔
642
          <igc-icon name=${iconName} collection="default"></igc-icon>
281✔
643
        </slot>
281✔
644
        <slot name="toggle-icon-expanded" ?hidden=${!iconHidden}></slot>
281✔
645
      </span>
281✔
646
    `;
281✔
647
  }
281✔
648

10✔
649
  protected _renderHelperText(): TemplateResult {
10✔
650
    return IgcValidationContainerComponent.create(this, {
281✔
651
      id: 'select-helper-text',
281✔
652
      slot: 'anchor',
281✔
653
      hasHelperText: true,
281✔
654
    });
281✔
655
  }
281✔
656

10✔
657
  protected _renderInputAnchor() {
10✔
658
    const value = this.selectedItem?.textContent?.trim();
281✔
659

281✔
660
    return html`
281✔
661
      <igc-input
281✔
662
        id="input"
281✔
663
        slot="anchor"
281✔
664
        role="combobox"
281✔
665
        readonly
281✔
666
        aria-controls="dropdown"
281✔
667
        aria-describedby="select-helper-text"
281✔
668
        aria-expanded=${this.open}
281✔
669
        exportparts="container: input, input: native-input, label, prefix, suffix"
281✔
670
        tabIndex=${this.disabled ? -1 : 0}
281✔
671
        value=${ifDefined(value)}
281✔
672
        placeholder=${ifDefined(this.placeholder)}
281✔
673
        label=${ifDefined(this.label)}
281✔
674
        .disabled=${this.disabled}
281✔
675
        .required=${this.required}
281✔
676
        .invalid=${this.invalid}
281✔
677
        .outlined=${this.outlined}
281✔
678
        @click=${this.handleAnchorClick}
281✔
679
      >
281✔
680
        ${this._renderInputSlots()} ${this._renderToggleIcon()}
281✔
681
      </igc-input>
281✔
682

281✔
683
      ${this._renderHelperText()}
281✔
684
    `;
281✔
685
  }
281✔
686

10✔
687
  protected _renderDropdown() {
10✔
688
    return html`
281✔
689
      <div part="base" .inert=${!this.open}>
281✔
690
        <div
281✔
691
          id="dropdown"
281✔
692
          role="listbox"
281✔
693
          part="list"
281✔
694
          aria-labelledby="input"
281✔
695
          @click=${this._handleClick}
281✔
696
        >
281✔
697
          <slot name="header"></slot>
281✔
698
          <slot></slot>
281✔
699
          <slot name="footer"></slot>
281✔
700
        </div>
281✔
701
      </div>
281✔
702
    `;
281✔
703
  }
281✔
704

10✔
705
  protected override render() {
10✔
706
    return html`
281✔
707
      <igc-popover
281✔
708
        ?open=${this.open}
281✔
709
        flip
281✔
710
        shift
281✔
711
        same-width
281✔
712
        .offset=${this.distance}
281✔
713
        .placement=${this.placement}
281✔
714
      >
281✔
715
        ${this._renderInputAnchor()} ${this._renderDropdown()}
281✔
716
      </igc-popover>
281✔
717
    `;
281✔
718
  }
281✔
719
}
10✔
720

10✔
721
declare global {
10✔
722
  interface HTMLElementTagNameMap {
10✔
723
    'igc-select': IgcSelectComponent;
10✔
724
  }
10✔
725
}
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

© 2026 Coveralls, Inc