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

IgniteUI / igniteui-webcomponents / 16437891720

22 Jul 2025 07:38AM UTC coverage: 98.291% (+0.01%) from 98.28%
16437891720

Pull #1786

github

web-flow
Merge 0d250dfba into 7c83039bd
Pull Request #1786: refactor: Form associated components validity behavior

4946 of 5196 branches covered (95.19%)

Branch coverage included in aggregate %.

312 of 316 new or added lines in 20 files covered. (98.73%)

12 existing lines in 3 files now uncovered.

32268 of 32665 relevant lines covered (98.78%)

1720.88 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;
290✔
147
  }
290✔
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);
32✔
204
    const item = this._getItem(this._formValue.value!);
32✔
205
    item ? this._setSelectedItem(item) : this._clearSelectedItem();
32✔
206
  }
32✔
207

10✔
208
  public get value(): string | undefined {
10✔
209
    return this._formValue.value;
879✔
210
  }
879✔
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(
264✔
263
      getItems<IgcSelectItemComponent>(this, IgcSelectItemComponent.tagName)
264✔
264
    );
264✔
265
  }
264✔
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;
351✔
277
  }
351✔
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, 'focusout', this._handleFocusOut);
83✔
318
  }
83✔
319

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

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

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

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

83✔
337
    this._validate();
83✔
338
  }
83✔
339

10✔
340
  //#endregion
10✔
341

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

10✔
428
  //#endregion
10✔
429

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

10✔
432
  private _handleFocusOut({ relatedTarget }: FocusEvent): void {
10✔
433
    if (this.contains(relatedTarget as Node)) {
59✔
434
      return;
34✔
435
    }
34✔
436
    super._handleBlur();
25✔
437
  }
59✔
438

10✔
439
  private _handleClick(event: PointerEvent): void {
10✔
440
    const item = findElementFromEventPath<IgcSelectItemComponent>(
9✔
441
      IgcSelectItemComponent.tagName,
9✔
442
      event
9✔
443
    );
9✔
444
    if (item && this._activeItems.includes(item)) {
9✔
445
      this._selectItem(item);
4✔
446
    }
4✔
447
  }
9✔
448

10✔
449
  private _handleChange(item: IgcSelectItemComponent): boolean {
10✔
450
    this._setTouchedState();
29✔
451
    return this.emitEvent('igcChange', { detail: item });
29✔
452
  }
29✔
453

10✔
454
  private _handleClosing(): void {
10✔
455
    this._hide(true);
3✔
456
  }
3✔
457

10✔
458
  protected override handleAnchorClick(): void {
10✔
459
    super.handleAnchorClick();
7✔
460
    this._focusItemOnOpen();
7✔
461
  }
7✔
462

10✔
463
  //#endregion
10✔
464

10✔
465
  //#region Internal API
10✔
466

10✔
467
  protected override _restoreDefaultValue(): void {
10✔
468
    super._restoreDefaultValue();
4✔
469
    this._formValue.setValueAndFormState(this._formValue.defaultValue);
4✔
470
    const item = this._getItem(this._formValue.value!);
4✔
471
    item ? this._setSelectedItem(item) : this._clearSelectedItem();
4✔
472
  }
4✔
473

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

81✔
479
    this._activeItem = item;
81✔
480
    this._activeItem.active = true;
81✔
481
  }
81✔
482

10✔
483
  private _setSelectedItem(
10✔
484
    item: IgcSelectItemComponent
59✔
485
  ): IgcSelectItemComponent {
59✔
486
    if (this._selectedItem) {
59✔
487
      this._selectedItem.selected = false;
19✔
488
    }
19✔
489

59✔
490
    this._selectedItem = item;
59✔
491
    this._selectedItem.selected = true;
59✔
492

59✔
493
    return this._selectedItem;
59✔
494
  }
59✔
495

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

45✔
506
    const shouldFocus = emit && this.open;
45✔
507
    const shouldHide = emit && !this.keepOpenOnSelect;
45✔
508

45✔
509
    if (this._selectedItem === item) {
45✔
510
      if (shouldFocus) this._input.focus();
11✔
511
      return this._selectedItem;
11✔
512
    }
11✔
513

34✔
514
    const newItem = this._setSelectedItem(item);
34✔
515
    this._activateItem(newItem);
34✔
516
    this._updateValue(newItem.value);
34✔
517

34✔
518
    if (emit) this._handleChange(newItem);
45✔
519
    if (shouldFocus) this._input.focus();
45✔
520
    if (shouldHide) this._hide(true);
45✔
521

34✔
522
    return this._selectedItem;
34✔
523
  }
45✔
524

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

10✔
533
  private _updateValue(value?: string): void {
10✔
534
    this._formValue.setValueAndFormState(value!);
69✔
535
  }
69✔
536

10✔
537
  private _clearSelectedItem(): void {
10✔
538
    if (this._selectedItem) {
14✔
539
      this._selectedItem.selected = false;
6✔
540
    }
6✔
541
    this._selectedItem = null;
14✔
542
  }
14✔
543

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

10✔
549
  private _getItem(value: string): IgcSelectItemComponent | undefined {
10✔
550
    return this.items.find((item) => item.value === value);
46✔
551
  }
46✔
552

10✔
553
  //#endregion
10✔
554

10✔
555
  //#region Public API
10✔
556

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

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

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

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

7✔
587
    if (item) {
7✔
588
      this._navigateToActiveItem(item);
4✔
589
    }
4✔
590

7✔
591
    return item ?? null;
7✔
592
  }
7✔
593

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

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

10✔
613
  //#endregion
10✔
614

10✔
615
  protected _renderInputSlots() {
10✔
616
    const prefix = this._slots.hasAssignedElements('prefix') ? 'prefix' : '';
283✔
617
    const suffix = this._slots.hasAssignedElements('suffix') ? 'suffix' : '';
283✔
618

283✔
619
    return html`
283✔
620
      <span slot=${prefix}>
283✔
621
        <slot name="prefix"></slot>
283✔
622
      </span>
283✔
623

283✔
624
      <span slot=${suffix}>
283✔
625
        <slot name="suffix"></slot>
283✔
626
      </span>
283✔
627
    `;
283✔
628
  }
283✔
629

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

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

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

10✔
654
  protected _renderInputAnchor() {
10✔
655
    const value = this.selectedItem?.textContent?.trim();
283✔
656

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

283✔
680
      ${this._renderHelperText()}
283✔
681
    `;
283✔
682
  }
283✔
683

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

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

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