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

IgniteUI / igniteui-webcomponents / 21251166657

22 Jan 2026 01:56PM UTC coverage: 98.097% (-0.1%) from 98.242%
21251166657

Pull #1969

github

web-flow
Merge 238523074 into 9769ee166
Pull Request #1969: Splitter component

5444 of 5736 branches covered (94.91%)

Branch coverage included in aggregate %.

734 of 744 new or added lines in 5 files covered. (98.66%)

44 existing lines in 7 files now uncovered.

36576 of 37099 relevant lines covered (98.59%)

1585.31 hits per line

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

99.28
/src/components/validation-container/validation-container.ts
1
import { html, LitElement, nothing, type TemplateResult } from 'lit';
33✔
2
import { property, state } from 'lit/decorators.js';
33✔
3
import { cache } from 'lit/directives/cache.js';
33✔
4
import { ifDefined } from 'lit/directives/if-defined.js';
33✔
5
import { addThemingController } from '../../theming/theming-controller.js';
33✔
6
import { createAbortHandle } from '../common/abort-handler.js';
33✔
7
import { registerComponent } from '../common/definitions/register.js';
33✔
8
import {
33✔
9
  type IgcFormControl,
33✔
10
  InternalInvalidEvent,
33✔
11
  InternalResetEvent,
33✔
12
} from '../common/mixins/forms/types.js';
33✔
13
import { partMap } from '../common/part-map.js';
33✔
14
import { isEmpty, toKebabCase } from '../common/util.js';
33✔
15
import IgcIconComponent from '../icon/icon.js';
33✔
16
import { all as inputThemes } from '../input/themes/themes.js';
33✔
17
import { styles as shared } from './themes/shared/validator.common.css.js';
33✔
18
import { styles } from './themes/validator.base.css.js';
33✔
19

33✔
20
/** Configuration for the validation container. */
33✔
21
interface ValidationContainerConfig {
33✔
22
  /** The id attribute for the validation container. */
33✔
23
  id?: string;
33✔
24
  /** Project the validation container to the given slot inside the host shadow DOM. */
33✔
25
  slot?: string;
33✔
26
  /** Additional part(s) that should be bound to the validation container. */
33✔
27
  part?: string;
33✔
28
  /** Whether the validation container should expose a helper-text slot. */
33✔
29
  hasHelperText?: boolean;
33✔
30
}
33✔
31

33✔
32
const VALIDATION_SLOTS_SELECTOR = 'slot:not([name="helper-text"])';
33✔
33
const ALL_SLOTS_SELECTOR = 'slot';
33✔
34
const QUERY_CONFIG: AssignedNodesOptions = { flatten: true };
33✔
35

33✔
36
function getValidationSlots(
3,599✔
37
  element: IgcValidationContainerComponent
3,599✔
38
): NodeListOf<HTMLSlotElement> {
3,599✔
39
  return element.renderRoot.querySelectorAll<HTMLSlotElement>(
3,599✔
40
    VALIDATION_SLOTS_SELECTOR
3,599✔
41
  );
3,599✔
42
}
3,599✔
43

33✔
44
function hasProjection(element: IgcValidationContainerComponent): boolean {
5,796✔
45
  const allSlots =
5,796✔
46
    element.renderRoot.querySelectorAll<HTMLSlotElement>(ALL_SLOTS_SELECTOR);
5,796✔
47
  return Array.from(allSlots).every((slot) =>
5,796✔
48
    isEmpty(slot.assignedElements(QUERY_CONFIG))
3,455✔
49
  );
5,796✔
50
}
5,796✔
51

33✔
52
function hasProjectedValidation(
3,599✔
53
  element: IgcValidationContainerComponent,
3,599✔
54
  slotName?: string
3,599✔
55
): boolean {
3,599✔
56
  const slots = Array.from(getValidationSlots(element));
3,599✔
57

3,599✔
58
  if (slotName) {
3,599✔
59
    return slots
608✔
60
      .filter((slot) => slot.name === slotName)
608✔
61
      .some((slot) => !isEmpty(slot.assignedElements(QUERY_CONFIG)));
608✔
62
  }
608✔
63

2,991✔
64
  return slots.some((slot) => !isEmpty(slot.assignedElements(QUERY_CONFIG)));
2,991✔
65
}
2,991✔
66

33✔
67
/* blazorSuppress */
33✔
68
/**
33✔
69
 * @element igc-validator
33✔
70
 *
33✔
71
 * @csspart helper-text - The base wrapper
33✔
72
 * @csspart validation-message - The validation error message container
33✔
73
 * @csspart validation-icon - The validation error icon
33✔
74
 */
33✔
75
export default class IgcValidationContainerComponent extends LitElement {
2,655✔
76
  public static readonly tagName = 'igc-validator';
2,655✔
77
  public static override styles = [styles, shared];
2,655✔
78

2,655✔
79
  protected readonly _themes = addThemingController(this, inputThemes);
2,655✔
80

2,655✔
81
  /* blazorSuppress */
2,655✔
82
  public static register(): void {
2,655✔
83
    registerComponent(IgcValidationContainerComponent, IgcIconComponent);
42✔
84
  }
42✔
85

2,655✔
86
  public static create(
2,655✔
87
    host: IgcFormControl,
6,642✔
88
    config: ValidationContainerConfig = {
6,642✔
89
      id: 'helper-text',
6,642✔
90
      hasHelperText: true,
6,642✔
91
    }
6,642✔
92
  ): TemplateResult {
6,642✔
93
    const helperText = config.hasHelperText
6,642✔
94
      ? html`<slot name="helper-text" slot="helper-text"></slot>`
6,642!
UNCOV
95
      : nothing;
×
96

6,642✔
97
    const validationSlots =
6,642✔
98
      IgcValidationContainerComponent.prototype._renderValidationSlots(
6,642✔
99
        host.validity,
6,642✔
100
        true
6,642✔
101
      );
6,642✔
102

6,642✔
103
    return html`
6,642✔
104
      <igc-validator
6,642✔
105
        id=${ifDefined(config.id)}
6,642✔
106
        part=${ifDefined(config.part)}
6,642✔
107
        slot=${ifDefined(config.slot)}
6,642✔
108
        ?invalid=${host.invalid}
6,642✔
109
        .target=${host}
6,642✔
110
        exportparts="helper-text, validation-message, validation-icon"
6,642✔
111
      >
6,642✔
112
        ${helperText}${validationSlots}
6,642✔
113
      </igc-validator>
6,642✔
114
    `;
6,642✔
115
  }
6,642✔
116

2,655✔
117
  private readonly _abortHandle = createAbortHandle();
2,655✔
118

2,655✔
119
  private _target!: IgcFormControl;
2,655✔
120

2,655✔
121
  @state()
2,655✔
122
  private _hasSlottedContent = false;
2,655✔
123

2,655✔
124
  @property({ type: Boolean })
2,655✔
125
  public invalid = false;
2,655✔
126

33✔
127
  @property({ attribute: false })
33✔
128
  public set target(value: IgcFormControl) {
33✔
129
    if (this._target === value) {
6,642✔
130
      return;
3,987✔
131
    }
3,987✔
132

2,655✔
133
    this._abortHandle.abort();
2,655✔
134
    const { signal } = this._abortHandle;
2,655✔
135

2,655✔
136
    this._target = value;
2,655✔
137
    this._target.addEventListener(InternalInvalidEvent, this, { signal });
2,655✔
138
    this._target.addEventListener(InternalResetEvent, this, { signal });
2,655✔
139
  }
6,642✔
140

33✔
141
  public get target(): IgcFormControl {
33✔
142
    return this._target;
16,339✔
143
  }
16,339✔
144

33✔
145
  protected override createRenderRoot(): HTMLElement | DocumentFragment {
33✔
146
    const root = super.createRenderRoot();
2,655✔
147
    root.addEventListener('slotchange', this);
2,655✔
148
    return root;
2,655✔
149
  }
2,655✔
150

33✔
151
  /** @internal */
33✔
152
  public handleEvent(event: Event): void {
33✔
153
    switch (event.type) {
3,251✔
154
      case InternalInvalidEvent:
3,251✔
155
        this.invalid = true;
189✔
156
        break;
189✔
157
      case InternalResetEvent:
3,251✔
158
        this.invalid = false;
71✔
159
        break;
71✔
160
      case 'slotchange': {
3,251✔
161
        const newHasSlottedContent = hasProjectedValidation(this);
2,991✔
162
        if (this._hasSlottedContent !== newHasSlottedContent) {
2,991✔
163
          this._hasSlottedContent = newHasSlottedContent;
71✔
164
        }
71✔
165
        break;
2,991✔
166
      }
2,991✔
167
    }
3,251✔
168

3,251✔
169
    this.requestUpdate();
3,251✔
170
  }
3,251✔
171

33✔
172
  protected _renderValidationMessage(slotName: string): TemplateResult {
33✔
173
    const hasProjectedIcon = hasProjectedValidation(this, slotName);
608✔
174
    const parts = { 'validation-message': true, empty: !hasProjectedIcon };
608✔
175
    const icon = hasProjectedIcon
608✔
176
      ? html`
61✔
177
          <igc-icon
547✔
178
            aria-hidden="true"
547✔
179
            name="error"
547✔
180
            part="validation-icon"
547✔
181
          ></igc-icon>
547✔
182
        `
547✔
183
      : nothing;
547✔
184

608✔
185
    return html`
608✔
186
      <div part=${partMap(parts)}>
608✔
187
        ${icon}
608✔
188
        <slot name=${slotName}></slot>
608✔
189
      </div>
608✔
190
    `;
608✔
191
  }
608✔
192

33✔
193
  protected *_renderValidationSlots(
33✔
194
    validity: ValidityState,
7,042✔
195
    projected = false
7,042✔
196
  ): Generator<TemplateResult> {
7,042✔
197
    if (!validity.valid) {
7,042✔
198
      yield projected
735✔
199
        ? html`<slot name="invalid" slot="invalid"></slot>`
432✔
200
        : this._renderValidationMessage('invalid');
303✔
201
    }
735✔
202

7,042✔
203
    for (const key in validity) {
7,042✔
204
      if (key !== 'valid' && validity[key as keyof ValidityState]) {
77,462✔
205
        const name = toKebabCase(key);
740✔
206
        yield projected
740✔
207
          ? html`<slot name=${name} slot=${name}></slot>`
435✔
208
          : this._renderValidationMessage(name);
305✔
209
      }
740✔
210
    }
77,462✔
211
  }
7,042✔
212

33✔
213
  protected _renderHelper(): TemplateResult | typeof nothing {
33✔
214
    return this.invalid && this._hasSlottedContent
5,796✔
215
      ? nothing
60✔
216
      : html`<slot name="helper-text"></slot>`;
5,736✔
217
  }
5,796✔
218

33✔
219
  protected override render(): TemplateResult {
33✔
220
    const slots = cache(
5,796✔
221
      this.invalid ? this._renderValidationSlots(this.target.validity) : nothing
5,796✔
222
    );
5,796✔
223

5,796✔
224
    return html`
5,796✔
225
      <div part=${partMap({ 'helper-text': true, empty: hasProjection(this) })}>
5,796✔
226
        ${slots}${this._renderHelper()}
5,796✔
227
      </div>
5,796✔
228
    `;
5,796✔
229
  }
5,796✔
230
}
33✔
231

33✔
232
declare global {
33✔
233
  interface HTMLElementTagNameMap {
33✔
234
    'igc-validator': IgcValidationContainerComponent;
33✔
235
  }
33✔
236
}
33✔
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