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

IgniteUI / igniteui-webcomponents / 16045088248

03 Jul 2025 08:10AM UTC coverage: 98.287% (+0.02%) from 98.271%
16045088248

Pull #1767

github

web-flow
Merge 120c56538 into d4a04cc87
Pull Request #1767: feat: Slot controller

4955 of 5205 branches covered (95.2%)

Branch coverage included in aggregate %.

694 of 700 new or added lines in 13 files covered. (99.14%)

11 existing lines in 2 files now uncovered.

31945 of 32338 relevant lines covered (98.78%)

1726.55 hits per line

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

99.27
/src/components/select/select.ts
1
import { html, type TemplateResult } from 'lit';
10✔
2
import {
10✔
3
  property,
10✔
4
  query,
10✔
5
  queryAssignedElements,
10✔
6
  state,
10✔
7
} from 'lit/decorators.js';
10✔
8
import { ifDefined } from 'lit/directives/if-defined.js';
10✔
9

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

10✔
67
export interface IgcSelectComponentEventMap {
10✔
68
  igcChange: CustomEvent<IgcSelectItemComponent>;
10✔
69
  // For analyzer meta only:
10✔
70
  /* skipWCPrefix */
10✔
71
  focus: FocusEvent;
10✔
72
  /* skipWCPrefix */
10✔
73
  blur: FocusEvent;
10✔
74
  igcOpening: CustomEvent<void>;
10✔
75
  igcOpened: CustomEvent<void>;
10✔
76
  igcClosing: CustomEvent<void>;
10✔
77
  igcClosed: CustomEvent<void>;
10✔
78
}
10✔
79

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

10✔
123
  /* blazorSuppress */
10✔
124
  public static register() {
10✔
125
    registerComponent(
10✔
126
      IgcSelectComponent,
10✔
127
      IgcIconComponent,
10✔
128
      IgcInputComponent,
10✔
129
      IgcPopoverComponent,
10✔
130
      IgcSelectGroupComponent,
10✔
131
      IgcSelectHeaderComponent,
10✔
132
      IgcSelectItemComponent
10✔
133
    );
10✔
134
  }
10✔
135

10✔
136
  protected override readonly _rootClickController = addRootClickController(
10✔
137
    this,
10✔
138
    {
10✔
139
      onHide: this.handleClosing,
10✔
140
    }
10✔
141
  );
10✔
142

10✔
143
  protected override readonly _formValue: FormValueOf<string | undefined> =
10✔
144
    createFormValueState<string | undefined>(this, {
10✔
145
      initialValue: undefined,
10✔
146
      transformers: {
10✔
147
        setValue: (value) => value || undefined,
10✔
148
        setDefaultValue: (value) => value || undefined,
10✔
149
      },
10✔
150
    });
10✔
151

10✔
152
  private _searchTerm = '';
10✔
153
  private _lastKeyTime = 0;
10✔
154

10✔
155
  private _rootScrollController = addRootScrollHandler(this, {
10✔
156
    hideCallback: this.handleClosing,
10✔
157
  });
10✔
158

10✔
159
  private get _activeItems() {
10✔
160
    return Array.from(
31✔
161
      getActiveItems<IgcSelectItemComponent>(
31✔
162
        this,
31✔
163
        IgcSelectItemComponent.tagName
31✔
164
      )
31✔
165
    );
31✔
166
  }
31✔
167

10✔
168
  @state()
10✔
169
  protected _selectedItem: IgcSelectItemComponent | null = null;
10✔
170

10✔
171
  @state()
10✔
172
  protected _activeItem!: IgcSelectItemComponent;
10✔
173

10✔
174
  protected override get __validators() {
10✔
175
    return selectValidators;
260✔
176
  }
260✔
177

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

10✔
181
  @queryAssignedElements({ slot: 'suffix' })
10✔
182
  protected _inputSuffix!: Array<HTMLElement>;
10✔
183

10✔
184
  @queryAssignedElements({ slot: 'prefix' })
10✔
185
  protected _inputPrefix!: Array<HTMLElement>;
10✔
186

10✔
187
  @queryAssignedElements({ slot: 'toggle-icon-expanded' })
10✔
188
  protected _expandedIconSlot!: Array<HTMLElement>;
10✔
189

10✔
190
  /* @tsTwoWayProperty(true, "igcChange", "detail.value", false) */
10✔
191
  /**
10✔
192
   * The value attribute of the control.
10✔
193
   * @attr
10✔
194
   */
10✔
195
  @property()
10✔
196
  public set value(value: string | undefined) {
10✔
197
    this._updateValue(value);
31✔
198
    const item = this.getItem(this._formValue.value!);
31✔
199
    item ? this.setSelectedItem(item) : this.clearSelectedItem();
31✔
200
  }
31✔
201

10✔
202
  public get value(): string | undefined {
10✔
203
    return this._formValue.value;
841✔
204
  }
841✔
205

10✔
206
  /**
10✔
207
   * The outlined attribute of the control.
10✔
208
   * @attr
10✔
209
   */
10✔
210
  @property({ reflect: true, type: Boolean })
10✔
211
  public outlined = false;
10✔
212

10✔
213
  /**
10✔
214
   * The autofocus attribute of the control.
10✔
215
   * @attr
10✔
216
   */
10✔
217
  @property({ type: Boolean })
10✔
218
  public override autofocus!: boolean;
10✔
219

10✔
220
  /**
10✔
221
   * The distance of the select dropdown from its input.
10✔
222
   * @attr
10✔
223
   */
10✔
224
  @property({ type: Number })
10✔
225
  public distance = 0;
10✔
226

10✔
227
  /**
10✔
228
   * The label attribute of the control.
10✔
229
   * @attr
10✔
230
   */
10✔
231
  @property()
10✔
232
  public label!: string;
10✔
233

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

10✔
241
  /** The preferred placement of the select dropdown around its input.
10✔
242
   * @attr
10✔
243
   */
10✔
244
  @property()
10✔
245
  public placement: PopoverPlacement = 'bottom-start';
10✔
246

10✔
247
  /**
10✔
248
   * Determines the behavior of the component during scrolling of the parent container.
10✔
249
   * @attr scroll-strategy
10✔
250
   */
10✔
251
  @property({ attribute: 'scroll-strategy' })
10✔
252
  public scrollStrategy: PopoverScrollStrategy = 'scroll';
10✔
253

10✔
254
  /** Returns the items of the igc-select component. */
10✔
255
  public get items() {
10✔
256
    return Array.from(
259✔
257
      getItems<IgcSelectItemComponent>(this, IgcSelectItemComponent.tagName)
259✔
258
    );
259✔
259
  }
259✔
260

10✔
261
  /** Returns the groups of the igc-select component. */
10✔
262
  public get groups() {
10✔
263
    return Array.from(
4✔
264
      getItems<IgcSelectGroupComponent>(this, IgcSelectGroupComponent.tagName)
4✔
265
    );
4✔
266
  }
4✔
267

10✔
268
  /** Returns the selected item from the dropdown or null.  */
10✔
269
  public get selectedItem() {
10✔
270
    return this._selectedItem;
349✔
271
  }
349✔
272

10✔
273
  @watch('scrollStrategy', { waitUntilFirstUpdate: true })
10✔
274
  protected scrollStrategyChanged() {
10✔
275
    this._rootScrollController.update({ resetListeners: true });
2✔
276
  }
2✔
277

10✔
278
  @watch('open', { waitUntilFirstUpdate: true })
10✔
279
  protected openChange() {
10✔
280
    this._rootClickController.update();
52✔
281
    this._rootScrollController.update();
52✔
282
  }
52✔
283

10✔
284
  constructor() {
10✔
285
    super();
83✔
286

83✔
287
    addThemingController(this, all);
83✔
288

83✔
289
    addKeybindings(this, {
83✔
290
      skip: () => this.disabled,
83✔
291
      bindingDefaults: { preventDefault: true, triggers: ['keydownRepeat'] },
83✔
292
    })
83✔
293
      .set([altKey, arrowDown], this.altArrowDown)
83✔
294
      .set([altKey, arrowUp], this.altArrowUp)
83✔
295
      .set(arrowDown, this.onArrowDown)
83✔
296
      .set(arrowUp, this.onArrowUp)
83✔
297
      .set(arrowLeft, this.onArrowUp)
83✔
298
      .set(arrowRight, this.onArrowDown)
83✔
299
      .set(tabKey, this.onTabKey, { preventDefault: false })
83✔
300
      .set(escapeKey, this.onEscapeKey)
83✔
301
      .set(homeKey, this.onHomeKey)
83✔
302
      .set(endKey, this.onEndKey)
83✔
303
      .set(spaceBar, this.onSpaceBarKey)
83✔
304
      .set(enterKey, this.onEnterKey);
83✔
305

83✔
306
    addSafeEventListener(this, 'keydown', this.handleSearch);
83✔
307
    addSafeEventListener(this, 'focusin', this.handleFocusIn);
83✔
308
    addSafeEventListener(this, 'focusout', this.handleFocusOut);
83✔
309
  }
83✔
310

10✔
311
  protected override createRenderRoot() {
10✔
312
    const root = super.createRenderRoot();
83✔
313
    root.addEventListener('slotchange', () => this.requestUpdate());
83✔
314
    return root;
83✔
315
  }
83✔
316

10✔
317
  protected override async firstUpdated() {
10✔
318
    await this.updateComplete;
83✔
319
    const selected = setInitialSelectionState(this.items);
83✔
320

83✔
321
    if (this.value && !selected) {
83✔
322
      this._selectItem(this.getItem(this.value), false);
1✔
323
    }
1✔
324

83✔
325
    if (selected && selected.value !== this.value) {
83✔
326
      this.defaultValue = selected.value;
3✔
327
      this._selectItem(selected, false);
3✔
328
    }
3✔
329

83✔
330
    if (this.autofocus) {
83✔
331
      this.focus();
1✔
332
    }
1✔
333

83✔
334
    this._updateValidity();
83✔
335
  }
83✔
336

10✔
337
  private handleFocusIn({ relatedTarget }: FocusEvent) {
10✔
338
    this._dirty = true;
56✔
339

56✔
340
    if (this.contains(relatedTarget as Node) || this.open) {
56✔
341
      return;
51✔
342
    }
51✔
343
  }
56✔
344

10✔
345
  private handleFocusOut({ relatedTarget }: FocusEvent) {
10✔
346
    if (this.contains(relatedTarget as Node)) {
56✔
347
      return;
34✔
348
    }
34✔
349

22✔
350
    this.checkValidity();
22✔
351
  }
56✔
352

10✔
353
  private handleClick(event: MouseEvent) {
10✔
354
    const item = findElementFromEventPath<IgcSelectItemComponent>(
9✔
355
      IgcSelectItemComponent.tagName,
9✔
356
      event
9✔
357
    );
9✔
358
    if (item && this._activeItems.includes(item)) {
9✔
359
      this._selectItem(item);
4✔
360
    }
4✔
361
  }
9✔
362

10✔
363
  private handleChange(item: IgcSelectItemComponent) {
10✔
364
    return this.emitEvent('igcChange', { detail: item });
29✔
365
  }
29✔
366

10✔
367
  private handleSearch(event: KeyboardEvent) {
10✔
368
    if (!/^.$/u.test(event.key)) {
94✔
369
      return;
72✔
370
    }
72✔
371

22✔
372
    event.preventDefault();
22✔
373
    const now = Date.now();
22✔
374

22✔
375
    if (now - this._lastKeyTime > 500) {
94✔
376
      this._searchTerm = '';
7✔
377
    }
7✔
378

22✔
379
    this._lastKeyTime = now;
22✔
380
    this._searchTerm += event.key.toLocaleLowerCase();
22✔
381

22✔
382
    const item = this._activeItems.find((item) =>
22✔
383
      item.textContent?.trim().toLocaleLowerCase().startsWith(this._searchTerm)
80✔
384
    );
22✔
385

22✔
386
    if (item) {
94✔
387
      this.open ? this.activateItem(item) : this._selectItem(item);
10✔
388
      this._activeItem.focus();
10✔
389
    }
10✔
390
  }
94✔
391

10✔
392
  protected override handleAnchorClick(): void {
10✔
393
    super.handleAnchorClick();
7✔
394
    this.focusItemOnOpen();
7✔
395
  }
7✔
396

10✔
397
  private onEnterKey() {
10✔
398
    this.open && this._activeItem
5✔
399
      ? this._selectItem(this._activeItem)
3✔
400
      : this.handleAnchorClick();
2✔
401
  }
5✔
402

10✔
403
  private onSpaceBarKey() {
10✔
404
    if (!this.open) {
2✔
405
      this.handleAnchorClick();
1✔
406
    }
1✔
407
  }
2✔
408

10✔
409
  private onArrowDown() {
10✔
410
    const item = getNextActiveItem(this.items, this._activeItem);
45✔
411
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
45✔
412
  }
45✔
413

10✔
414
  private onArrowUp() {
10✔
415
    const item = getPreviousActiveItem(this.items, this._activeItem);
11✔
416
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
11✔
417
  }
11✔
418

10✔
419
  private altArrowDown() {
10✔
420
    if (!this.open) {
1✔
421
      this._show(true);
1✔
422
      this.focusItemOnOpen();
1✔
423
    }
1✔
424
  }
1✔
425

10✔
426
  private async altArrowUp() {
10✔
427
    if (this.open && (await this._hide(true))) {
1✔
428
      this._input.focus();
1✔
429
    }
1✔
430
  }
1✔
431

10✔
432
  protected async onEscapeKey() {
10✔
433
    if (await this._hide(true)) {
2✔
434
      this._input.focus();
1✔
435
    }
1✔
436
  }
2✔
437

10✔
438
  private onTabKey(event: KeyboardEvent) {
10✔
439
    if (this.open) {
3✔
440
      event.preventDefault();
3✔
441
      this._selectItem(this._activeItem ?? this._selectedItem);
3✔
442
      this._hide(true);
3✔
443
    }
3✔
444
  }
3✔
445

10✔
446
  protected onHomeKey() {
10✔
447
    const item = this._activeItems.at(0);
2✔
448
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
2✔
449
  }
2✔
450

10✔
451
  protected onEndKey() {
10✔
452
    const item = this._activeItems.at(-1);
2✔
453
    this.open ? this._navigateToActiveItem(item) : this._selectItem(item);
2✔
454
  }
2✔
455

10✔
456
  protected handleClosing() {
10✔
457
    this._hide(true);
3✔
458
  }
3✔
459

10✔
460
  private activateItem(item: IgcSelectItemComponent) {
10✔
461
    if (this._activeItem) {
82✔
462
      this._activeItem.active = false;
53✔
463
    }
53✔
464

82✔
465
    this._activeItem = item;
82✔
466
    this._activeItem.active = true;
82✔
467
  }
82✔
468

10✔
469
  private setSelectedItem(item: IgcSelectItemComponent) {
10✔
470
    if (this._selectedItem) {
56✔
471
      this._selectedItem.selected = false;
16✔
472
    }
16✔
473

56✔
474
    this._selectedItem = item;
56✔
475
    this._selectedItem.selected = true;
56✔
476

56✔
477
    return this._selectedItem;
56✔
478
  }
56✔
479

10✔
480
  private _selectItem(item?: IgcSelectItemComponent, emit = true) {
10✔
481
    if (!item) {
45!
UNCOV
482
      this.clearSelectedItem();
×
UNCOV
483
      this._updateValue();
×
UNCOV
484
      return null;
×
485
    }
×
486

45✔
487
    const shouldFocus = emit && this.open;
45✔
488
    const shouldHide = emit && !this.keepOpenOnSelect;
45✔
489

45✔
490
    if (this._selectedItem === item) {
45✔
491
      if (shouldFocus) this._input.focus();
10✔
492
      return this._selectedItem;
10✔
493
    }
10✔
494

35✔
495
    const newItem = this.setSelectedItem(item);
35✔
496
    this.activateItem(newItem);
35✔
497
    this._updateValue(newItem.value);
35✔
498

35✔
499
    if (emit) this.handleChange(newItem);
45✔
500
    if (shouldFocus) this._input.focus();
45✔
501
    if (shouldHide) this._hide(true);
45✔
502

35✔
503
    return this._selectedItem;
35✔
504
  }
45✔
505

10✔
506
  private _navigateToActiveItem(item?: IgcSelectItemComponent) {
10✔
507
    if (item) {
44✔
508
      this.activateItem(item);
44✔
509
      this._activeItem.focus({ preventScroll: true });
44✔
510
      item.scrollIntoView({ behavior: 'auto', block: 'nearest' });
44✔
511
    }
44✔
512
  }
44✔
513

10✔
514
  private _updateValue(value?: string) {
10✔
515
    this._formValue.setValueAndFormState(value!);
69✔
516
    this._validate();
69✔
517
  }
69✔
518

10✔
519
  private clearSelectedItem() {
10✔
520
    if (this._selectedItem) {
13✔
521
      this._selectedItem.selected = false;
5✔
522
    }
5✔
523
    this._selectedItem = null;
13✔
524
  }
13✔
525

10✔
526
  private async focusItemOnOpen() {
10✔
527
    await this.updateComplete;
8✔
528
    (this._selectedItem || this._activeItem)?.focus();
8!
529
  }
8✔
530

10✔
531
  protected getItem(value: string) {
10✔
532
    return this.items.find((item) => item.value === value);
41✔
533
  }
41✔
534

10✔
535
  /* alternateName: focusComponent */
10✔
536
  /** Sets focus on the component. */
10✔
537
  public override focus(options?: FocusOptions) {
10✔
538
    this._input.focus(options);
3✔
539
  }
3✔
540

10✔
541
  /* alternateName: blurComponent */
10✔
542
  /** Removes focus from the component. */
10✔
543
  public override blur() {
10✔
544
    this._input.blur();
1✔
545
  }
1✔
546

10✔
547
  /** Checks the validity of the control and moves the focus to it if it is not valid. */
10✔
548
  public override reportValidity() {
10✔
549
    const valid = super.reportValidity();
4✔
550
    if (!valid) this._input.focus();
4✔
551
    return valid;
4✔
552
  }
4✔
553

10✔
554
  /* blazorSuppress */
10✔
555
  /** Navigates to the item with the specified value. If it exists, returns the found item, otherwise - null. */
10✔
556
  public navigateTo(value: string): IgcSelectItemComponent | null;
10✔
557
  /* blazorSuppress */
10✔
558
  /** Navigates to the item at the specified index. If it exists, returns the found item, otherwise - null. */
10✔
559
  public navigateTo(index: number): IgcSelectItemComponent | null;
10✔
560
  /* blazorSuppress */
10✔
561
  /** Navigates to the specified item. If it exists, returns the found item, otherwise - null. */
10✔
562
  public navigateTo(value: string | number): IgcSelectItemComponent | null {
10✔
563
    const item = isString(value) ? this.getItem(value) : this.items[value];
7✔
564

7✔
565
    if (item) {
7✔
566
      this._navigateToActiveItem(item);
4✔
567
    }
4✔
568

7✔
569
    return item ?? null;
7✔
570
  }
7✔
571

10✔
572
  /* blazorSuppress */
10✔
573
  /** Selects the item with the specified value. If it exists, returns the found item, otherwise - null. */
10✔
574
  public select(value: string): IgcSelectItemComponent | null;
10✔
575
  /* blazorSuppress */
10✔
576
  /** Selects the item at the specified index. If it exists, returns the found item, otherwise - null. */
10✔
577
  public select(index: number): IgcSelectItemComponent | null;
10✔
578
  /* blazorSuppress */
10✔
579
  /** Selects the specified item. If it exists, returns the found item, otherwise - null. */
10✔
580
  public select(value: string | number): IgcSelectItemComponent | null {
10✔
581
    const item = isString(value) ? this.getItem(value) : this.items[value];
8✔
582
    return item ? this._selectItem(item, false) : null;
8✔
583
  }
8✔
584

10✔
585
  /**  Resets the current value and selection of the component. */
10✔
586
  public clearSelection() {
10✔
587
    this._updateValue();
3✔
588
    this.clearSelectedItem();
3✔
589
  }
3✔
590

10✔
591
  protected renderInputSlots() {
10✔
592
    const prefixName = isEmpty(this._inputPrefix) ? '' : 'prefix';
281✔
593
    const suffixName = isEmpty(this._inputSuffix) ? '' : 'suffix';
281✔
594

281✔
595
    return html`
281✔
596
      <span slot=${prefixName}>
281✔
597
        <slot name="prefix"></slot>
281✔
598
      </span>
281✔
599

281✔
600
      <span slot=${suffixName}>
281✔
601
        <slot name="suffix"></slot>
281✔
602
      </span>
281✔
603
    `;
281✔
604
  }
281✔
605

10✔
606
  protected renderToggleIcon() {
10✔
607
    const parts = { 'toggle-icon': true, filled: !!this.value };
281✔
608
    const iconHidden = this.open && !isEmpty(this._expandedIconSlot);
281✔
609
    const iconExpandedHidden = !iconHidden;
281✔
610

281✔
611
    return html`
281✔
612
      <span slot="suffix" part=${partMap(parts)} aria-hidden="true">
281✔
613
        <slot name="toggle-icon" ?hidden=${iconHidden}>
281✔
614
          <igc-icon
281✔
615
            name=${this.open ? 'input_collapse' : 'input_expand'}
281✔
616
            collection="default"
281✔
617
          ></igc-icon>
281✔
618
        </slot>
281✔
619
        <slot name="toggle-icon-expanded" ?hidden=${iconExpandedHidden}></slot>
281✔
620
      </span>
281✔
621
    `;
281✔
622
  }
281✔
623

10✔
624
  protected renderHelperText(): TemplateResult {
10✔
625
    return IgcValidationContainerComponent.create(this, {
281✔
626
      id: 'select-helper-text',
281✔
627
      slot: 'anchor',
281✔
628
      hasHelperText: true,
281✔
629
    });
281✔
630
  }
281✔
631

10✔
632
  protected renderInputAnchor() {
10✔
633
    const value = this.selectedItem?.textContent?.trim();
281✔
634

281✔
635
    return html`
281✔
636
      <igc-input
281✔
637
        id="input"
281✔
638
        slot="anchor"
281✔
639
        role="combobox"
281✔
640
        readonly
281✔
641
        aria-controls="dropdown"
281✔
642
        aria-describedby="select-helper-text"
281✔
643
        aria-expanded=${this.open ? 'true' : 'false'}
281✔
644
        exportparts="container: input, input: native-input, label, prefix, suffix"
281✔
645
        tabIndex=${this.disabled ? -1 : 0}
281✔
646
        value=${ifDefined(value)}
281✔
647
        placeholder=${ifDefined(this.placeholder)}
281✔
648
        label=${ifDefined(this.label)}
281✔
649
        .disabled=${this.disabled}
281✔
650
        .required=${this.required}
281✔
651
        .invalid=${this.invalid}
281✔
652
        .outlined=${this.outlined}
281✔
653
        @click=${this.handleAnchorClick}
281✔
654
      >
281✔
655
        ${this.renderInputSlots()} ${this.renderToggleIcon()}
281✔
656
      </igc-input>
281✔
657

281✔
658
      ${this.renderHelperText()}
281✔
659
    `;
281✔
660
  }
281✔
661

10✔
662
  protected renderDropdown() {
10✔
663
    return html`<div part="base" .inert=${!this.open}>
281✔
664
      <div
281✔
665
        id="dropdown"
281✔
666
        role="listbox"
281✔
667
        part="list"
281✔
668
        aria-labelledby="input"
281✔
669
        @click=${this.handleClick}
281✔
670
      >
281✔
671
        <slot name="header"></slot>
281✔
672
        <slot></slot>
281✔
673
        <slot name="footer"></slot>
281✔
674
      </div>
281✔
675
    </div>`;
281✔
676
  }
281✔
677

10✔
678
  protected override render() {
10✔
679
    return html`<igc-popover
281✔
680
      ?open=${this.open}
281✔
681
      flip
281✔
682
      shift
281✔
683
      same-width
281✔
684
      .offset=${this.distance}
281✔
685
      .placement=${this.placement}
281✔
686
      >${this.renderInputAnchor()} ${this.renderDropdown()}
281✔
687
    </igc-popover>`;
281✔
688
  }
281✔
689
}
10✔
690

10✔
691
declare global {
10✔
692
  interface HTMLElementTagNameMap {
10✔
693
    'igc-select': IgcSelectComponent;
10✔
694
  }
10✔
695
}
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