Skip to content

Commit d4a04cc

Browse files
authored
refactor(validation-container): Improved code style and types (#1773)
Skip the generation of the `valid` slot inside the container since it is not used and is needless DOM.
1 parent f2abd36 commit d4a04cc

File tree

1 file changed

+76
-46
lines changed

1 file changed

+76
-46
lines changed

src/components/validation-container/validation-container.ts

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { styles as shared } from './themes/shared/validator.common.css.js';
1111
import { all } from './themes/themes.js';
1212
import { styles } from './themes/validator.base.css.js';
1313

14+
/** Configuration for the validation container. */
1415
interface ValidationContainerConfig {
1516
/** The id attribute for the validation container. */
1617
id?: string;
@@ -22,29 +23,39 @@ interface ValidationContainerConfig {
2223
hasHelperText?: boolean;
2324
}
2425

25-
function getValidationSlots(element: IgcValidationContainerComponent) {
26+
const VALIDATION_SLOTS_SELECTOR = 'slot:not([name="helper-text"])';
27+
const ALL_SLOTS_SELECTOR = 'slot';
28+
29+
function getValidationSlots(
30+
element: IgcValidationContainerComponent
31+
): NodeListOf<HTMLSlotElement> {
2632
return element.renderRoot.querySelectorAll<HTMLSlotElement>(
27-
"slot:not([name='helper-text'])"
33+
VALIDATION_SLOTS_SELECTOR
2834
);
2935
}
3036

31-
function hasProjection(element: IgcValidationContainerComponent) {
32-
return Array.from(
33-
element.renderRoot.querySelectorAll<HTMLSlotElement>('slot')
34-
).every((slot) => isEmpty(slot.assignedElements({ flatten: true })));
37+
function hasProjection(element: IgcValidationContainerComponent): boolean {
38+
const allSlots =
39+
element.renderRoot.querySelectorAll<HTMLSlotElement>(ALL_SLOTS_SELECTOR);
40+
return Array.from(allSlots).every((slot) =>
41+
isEmpty(slot.assignedElements({ flatten: true }))
42+
);
3543
}
3644

3745
function hasProjectedValidation(
3846
element: IgcValidationContainerComponent,
3947
slotName?: string
40-
) {
41-
const config: AssignedNodesOptions = { flatten: true };
48+
): boolean {
4249
const slots = Array.from(getValidationSlots(element));
43-
return slotName
44-
? slots
45-
.filter((slot) => slot.name === slotName)
46-
.some((slot) => slot.assignedElements(config).length > 0)
47-
: slots.some((slot) => slot.assignedElements(config).length > 0);
50+
const config: AssignedNodesOptions = { flatten: true };
51+
52+
if (slotName) {
53+
return slots
54+
.filter((slot) => slot.name === slotName)
55+
.some((slot) => !isEmpty(slot.assignedElements(config)));
56+
}
57+
58+
return slots.some((slot) => !isEmpty(slot.assignedElements(config)));
4859
}
4960

5061
/* blazorSuppress */
@@ -60,7 +71,7 @@ export default class IgcValidationContainerComponent extends LitElement {
6071
public static override styles = [styles, shared];
6172

6273
/* blazorSuppress */
63-
public static register() {
74+
public static register(): void {
6475
registerComponent(IgcValidationContainerComponent, IgcIconComponent);
6576
}
6677

@@ -71,21 +82,26 @@ export default class IgcValidationContainerComponent extends LitElement {
7182
hasHelperText: true,
7283
}
7384
): TemplateResult {
74-
const { renderValidationSlots } = IgcValidationContainerComponent.prototype;
7585
const helperText = config.hasHelperText
7686
? html`<slot name="helper-text" slot="helper-text"></slot>`
77-
: null;
87+
: nothing;
88+
89+
const validationSlots =
90+
IgcValidationContainerComponent.prototype._renderValidationSlots(
91+
host.validity,
92+
true
93+
);
7894

7995
return html`
8096
<igc-validator
8197
id=${ifDefined(config.id)}
8298
part=${ifDefined(config.part)}
8399
slot=${ifDefined(config.slot)}
84-
.target=${host}
85100
?invalid=${host.invalid}
101+
.target=${host}
86102
exportparts="helper-text validation-message validation-icon"
87103
>
88-
${helperText}${renderValidationSlots(host.validity, true)}
104+
${helperText}${validationSlots}
89105
</igc-validator>
90106
`;
91107
}
@@ -109,7 +125,7 @@ export default class IgcValidationContainerComponent extends LitElement {
109125
this._target.addEventListener('invalid', this);
110126
}
111127

112-
public get target() {
128+
public get target(): IgcFormControl {
113129
return this._target;
114130
}
115131

@@ -118,73 +134,87 @@ export default class IgcValidationContainerComponent extends LitElement {
118134
addThemingController(this, all);
119135
}
120136

121-
protected override createRenderRoot() {
137+
protected override createRenderRoot(): HTMLElement | DocumentFragment {
122138
const root = super.createRenderRoot();
123139
root.addEventListener('slotchange', this);
124140
return root;
125141
}
126142

127-
public handleEvent({ type }: Event) {
128-
const isInvalid = type === 'invalid';
129-
const isSlotChange = type === 'slotchange';
130-
131-
if (isInvalid || isSlotChange) {
132-
this.invalid = isInvalid ? true : this.invalid;
133-
this._hasSlottedContent = hasProjectedValidation(this);
143+
/** @internal */
144+
public handleEvent(event: Event): void {
145+
switch (event.type) {
146+
case 'invalid':
147+
if (!this.invalid) {
148+
this.invalid = true;
149+
}
150+
break;
151+
case 'slotchange': {
152+
const newHasSlottedContent = hasProjectedValidation(this);
153+
if (this._hasSlottedContent !== newHasSlottedContent) {
154+
this._hasSlottedContent = newHasSlottedContent;
155+
}
156+
break;
157+
}
134158
}
135159

136160
this.requestUpdate();
137161
}
138162

139-
protected renderValidationMessage(slotName: string) {
140-
const icon = hasProjectedValidation(this, slotName)
163+
protected _renderValidationMessage(slotName: string): TemplateResult {
164+
const hasProjectedIcon = hasProjectedValidation(this, slotName);
165+
const parts = { 'validation-message': true, empty: !hasProjectedIcon };
166+
const icon = hasProjectedIcon
141167
? html`
142168
<igc-icon
143169
aria-hidden="true"
144170
name="error"
145-
collection="default"
146171
part="validation-icon"
147172
></igc-icon>
148173
`
149-
: null;
174+
: nothing;
150175

151176
return html`
152-
<div part=${partMap({ 'validation-message': true, empty: !icon })}>
177+
<div part=${partMap(parts)}>
153178
${icon}
154179
<slot name=${slotName}></slot>
155180
</div>
156181
`;
157182
}
158183

159-
protected *renderValidationSlots(validity: ValidityState, projected = false) {
184+
protected *_renderValidationSlots(
185+
validity: ValidityState,
186+
projected = false
187+
): Generator<TemplateResult> {
188+
if (!validity.valid) {
189+
yield projected
190+
? html`<slot name="invalid" slot="invalid"></slot>`
191+
: this._renderValidationMessage('invalid');
192+
}
193+
160194
for (const key in validity) {
161-
if (key === 'valid' && !validity[key]) {
162-
yield projected
163-
? html`<slot name="invalid" slot="invalid"></slot>`
164-
: this.renderValidationMessage('invalid');
165-
} else if (validity[key as keyof ValidityState]) {
195+
if (key !== 'valid' && validity[key as keyof ValidityState]) {
166196
const name = toKebabCase(key);
167-
168197
yield projected
169198
? html`<slot name=${name} slot=${name}></slot>`
170-
: this.renderValidationMessage(name);
199+
: this._renderValidationMessage(name);
171200
}
172201
}
173202
}
174203

175-
protected renderHelper() {
204+
protected _renderHelper(): TemplateResult | typeof nothing {
176205
return this.invalid && this._hasSlottedContent
177206
? nothing
178207
: html`<slot name="helper-text"></slot>`;
179208
}
180209

181-
protected override render() {
210+
protected override render(): TemplateResult {
211+
const slots = this.invalid
212+
? this._renderValidationSlots(this.target.validity)
213+
: nothing;
214+
182215
return html`
183216
<div part=${partMap({ 'helper-text': true, empty: hasProjection(this) })}>
184-
${this.invalid
185-
? this.renderValidationSlots(this.target.validity)
186-
: nothing}
187-
${this.renderHelper()}
217+
${slots}${this._renderHelper()}
188218
</div>
189219
`;
190220
}

0 commit comments

Comments
 (0)