Skip to content

Commit eb7f33b

Browse files
committed
refactor(radio): Use SlotController
1 parent f3e8502 commit eb7f33b

File tree

4 files changed

+72
-111
lines changed

4 files changed

+72
-111
lines changed

src/components/checkbox/checkbox-base.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ export class IgcCheckboxBaseComponent extends FormAssociatedCheckboxRequiredMixi
4040

4141
protected readonly _slots = addSlotController(this, {
4242
slots: setSlots('helper-text', 'value-missing', 'custom-error', 'invalid'),
43-
onChange: () => {
44-
this._hideLabel = !this._slots.hasAssignedNodes('[default]');
45-
},
43+
onChange: this._handleSlotChange,
4644
initial: true,
4745
});
4846

@@ -68,7 +66,7 @@ export class IgcCheckboxBaseComponent extends FormAssociatedCheckboxRequiredMixi
6866
public set value(value: string) {
6967
this._value = value;
7068
if (this.checked) {
71-
this._setFormValue(this._value);
69+
this._setFormValue(this._value || 'on');
7270
}
7371
}
7472

@@ -115,6 +113,10 @@ export class IgcCheckboxBaseComponent extends FormAssociatedCheckboxRequiredMixi
115113
this._input.blur();
116114
}
117115

116+
protected _handleSlotChange(): void {
117+
this._hideLabel = !this._slots.hasAssignedNodes('[default]');
118+
}
119+
118120
protected _handleClick(event: PointerEvent): void {
119121
event.stopPropagation();
120122

src/components/radio-group/radio-group.ts

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { html, LitElement } from 'lit';
2-
import { property, queryAssignedElements } from 'lit/decorators.js';
2+
import { property } from 'lit/decorators.js';
33
import { addThemingController } from '../../theming/theming-controller.js';
44
import { addInternalsController } from '../common/controllers/internals.js';
55
import { createMutationController } from '../common/controllers/mutation-observer.js';
6+
import { addSlotController, setSlots } from '../common/controllers/slot.js';
67
import { registerComponent } from '../common/definitions/register.js';
78
import IgcRadioComponent from '../radio/radio.js';
89
import type { ContentOrientation } from '../types.js';
@@ -32,13 +33,18 @@ export default class IgcRadioGroupComponent extends LitElement {
3233
},
3334
});
3435

36+
private readonly _slots = addSlotController(this, {
37+
slots: setSlots(),
38+
onChange: this._handleSlotChange,
39+
initial: true,
40+
});
41+
42+
private _radios: IgcRadioComponent[] = [];
43+
3544
private _defaultValue!: string;
3645
private _name!: string;
3746
private _value!: string;
3847

39-
@queryAssignedElements({ selector: 'igc-radio', flatten: true })
40-
private _radios!: NodeListOf<IgcRadioComponent>;
41-
4248
/**
4349
* Alignment of the radio controls inside this group.
4450
* @attr
@@ -84,27 +90,12 @@ export default class IgcRadioGroupComponent extends LitElement {
8490

8591
public get value(): string {
8692
if (this._radios.length) {
87-
this._value =
88-
Array.from(this._radios).find((radio) => radio.checked)?.value ?? '';
93+
this._value = this._radios.find((radio) => radio.checked)?.value ?? '';
8994
}
9095

9196
return this._value;
9297
}
9398

94-
private _observerCallback(): void {
95-
const radios = Array.from(this._radios);
96-
97-
this._internals.setState(
98-
'disabled',
99-
radios.every((radio) => radio.disabled)
100-
);
101-
102-
this._internals.setState(
103-
'label-before',
104-
radios.some((radio) => radio.labelPosition === 'before')
105-
);
106-
}
107-
10899
constructor() {
109100
super();
110101

@@ -120,13 +111,7 @@ export default class IgcRadioGroupComponent extends LitElement {
120111
});
121112
}
122113

123-
protected override createRenderRoot() {
124-
const root = super.createRenderRoot();
125-
root.addEventListener('slotchange', () => this._setCSSGridVars());
126-
return root;
127-
}
128-
129-
protected override firstUpdated() {
114+
protected override firstUpdated(): void {
130115
const radios = Array.from(this._radios);
131116
const allRadiosUnchecked = radios.every((radio) => !radio.checked);
132117

@@ -139,39 +124,52 @@ export default class IgcRadioGroupComponent extends LitElement {
139124
}
140125
}
141126

142-
private _setCSSGridVars() {
143-
const slot = this.renderRoot.querySelector('slot');
144-
if (slot) {
145-
this.style.setProperty(
146-
'--layout-count',
147-
`${slot.assignedElements({ flatten: true }).length}`
148-
);
149-
}
127+
private _observerCallback(): void {
128+
const disabled = this._radios.every((radio) => radio.disabled);
129+
const labeBefore = this._radios.some(
130+
(radio) => radio.labelPosition === 'before'
131+
);
132+
133+
this._internals.setState('disabled', disabled);
134+
this._internals.setState('label-before', labeBefore);
135+
}
136+
137+
private _handleSlotChange(): void {
138+
this._radios = this._slots.getAssignedElements('[default]', {
139+
selector: IgcRadioComponent.tagName,
140+
flatten: true,
141+
});
142+
143+
const elements = this._slots.getAssignedElements('[default]', {
144+
flatten: true,
145+
});
146+
147+
this.style.setProperty('--layout-count', elements.length.toString());
150148
}
151149

152-
private _setRadiosDefaultChecked() {
150+
private _setRadiosDefaultChecked(): void {
153151
if (this._defaultValue) {
154152
for (const radio of this._radios) {
155153
radio.defaultChecked = radio.value === this._defaultValue;
156154
}
157155
}
158156
}
159157

160-
private _setRadiosName() {
158+
private _setRadiosName(): void {
161159
if (this._name) {
162160
for (const radio of this._radios) {
163161
radio.name = this._name;
164162
}
165163
}
166164
}
167165

168-
private _setDefaultValue() {
166+
private _setDefaultValue(): void {
169167
for (const radio of this._radios) {
170168
radio.toggleAttribute('checked', radio.checked);
171169
}
172170
}
173171

174-
private _setSelectedRadio() {
172+
private _setSelectedRadio(): void {
175173
for (const radio of this._radios) {
176174
radio.checked = radio.value === this._value;
177175
}

src/components/radio/radio.spec.ts

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -109,33 +109,6 @@ describe('Radio Component', () => {
109109
expect(input).dom.to.equal(`<input value="${value}"/>`, DIFF_OPTIONS);
110110
});
111111

112-
it('sets the checked property successfully', async () => {
113-
const DIFF_OPTIONS = {
114-
ignoreAttributes: [
115-
'id',
116-
'part',
117-
'aria-disabled',
118-
'aria-labelledby',
119-
'tabindex',
120-
'type',
121-
],
122-
};
123-
124-
radio.checked = true;
125-
expect(radio.checked).to.be.true;
126-
await elementUpdated(radio);
127-
expect(input).dom.to.equal(`<input aria-checked="true" />`, DIFF_OPTIONS);
128-
129-
radio.checked = false;
130-
expect(radio.checked).to.be.false;
131-
await elementUpdated(radio);
132-
133-
expect(input).dom.to.equal(
134-
`<input aria-checked="false" />`,
135-
DIFF_OPTIONS
136-
);
137-
});
138-
139112
it('sets the disabled property successfully', async () => {
140113
const DIFF_OPTIONS = {
141114
ignoreAttributes: [
@@ -151,19 +124,13 @@ describe('Radio Component', () => {
151124
radio.disabled = true;
152125
expect(radio.disabled).to.be.true;
153126
await elementUpdated(radio);
154-
expect(input).dom.to.equal(
155-
`<input disabled aria-disabled="true" />`,
156-
DIFF_OPTIONS
157-
);
127+
expect(input).dom.to.equal('<input disabled />', DIFF_OPTIONS);
158128

159129
radio.disabled = false;
160130
expect(radio.disabled).to.be.false;
161131
await elementUpdated(radio);
162132

163-
expect(input).dom.to.equal(
164-
`<input aria-disabled="false" />`,
165-
DIFF_OPTIONS
166-
);
133+
expect(input).dom.to.equal('<input />', DIFF_OPTIONS);
167134
});
168135

169136
it('sets the required property successfully', async () => {

src/components/radio/radio.ts

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { html, LitElement, type TemplateResult } from 'lit';
1+
import { html, LitElement, nothing, type TemplateResult } from 'lit';
22
import { property, query, queryAssignedNodes, state } from 'lit/decorators.js';
33
import { ifDefined } from 'lit/directives/if-defined.js';
44
import { live } from 'lit/directives/live.js';
5-
65
import { addThemingController } from '../../theming/theming-controller.js';
76
import { addKeyboardFocusRing } from '../common/controllers/focus-ring.js';
87
import {
@@ -12,6 +11,7 @@ import {
1211
arrowRight,
1312
arrowUp,
1413
} from '../common/controllers/key-bindings.js';
14+
import { addSlotController, setSlots } from '../common/controllers/slot.js';
1515
import { registerComponent } from '../common/definitions/register.js';
1616
import type { Constructor } from '../common/mixins/constructor.js';
1717
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
@@ -22,14 +22,7 @@ import {
2222
type FormValueOf,
2323
} from '../common/mixins/forms/form-value.js';
2424
import { partMap } from '../common/part-map.js';
25-
import {
26-
createCounter,
27-
isDefined,
28-
isEmpty,
29-
isLTR,
30-
last,
31-
wrap,
32-
} from '../common/util.js';
25+
import { isDefined, isEmpty, isLTR, last, wrap } from '../common/util.js';
3326
import type { ToggleLabelPosition } from '../types.js';
3427
import IgcValidationContainerComponent from '../validation-container/validation-container.js';
3528
import { styles } from './themes/radio.base.css.js';
@@ -52,6 +45,8 @@ export interface IgcRadioComponentEventMap {
5245
blur: FocusEvent;
5346
}
5447

48+
let nextId = 1;
49+
5550
/**
5651
* @element igc-radio
5752
*
@@ -80,22 +75,25 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix
8075
registerComponent(IgcRadioComponent, IgcValidationContainerComponent);
8176
}
8277

83-
private static readonly increment = createCounter();
84-
8578
protected override get __validators() {
8679
return radioValidators;
8780
}
8881

82+
private readonly _inputId = `radio-${nextId++}`;
83+
private readonly _labelId = `radio-label-${this._inputId}`;
84+
private readonly _focusRingManager = addKeyboardFocusRing(this);
85+
private readonly _slots = addSlotController(this, {
86+
slots: setSlots('helper-text', 'value-missing', 'custom-error', 'invalid'),
87+
onChange: this._handleSlotChange,
88+
initial: true,
89+
});
90+
8991
protected override readonly _formValue: FormValueOf<boolean> =
9092
createFormValueState(this, {
9193
initialValue: false,
9294
transformers: defaultBooleanTransformers,
9395
});
9496

95-
private readonly _inputId = `radio-${IgcRadioComponent.increment()}`;
96-
private readonly _labelId = `radio-label-${this._inputId}`;
97-
private readonly _focusRingManager = addKeyboardFocusRing(this);
98-
9997
protected _value!: string;
10098

10199
@query('input', true)
@@ -202,16 +200,6 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix
202200
.set(arrowDown, () => this._navigate(1));
203201
}
204202

205-
protected override createRenderRoot(): HTMLElement | DocumentFragment {
206-
const root = super.createRenderRoot();
207-
this._hideLabel = isEmpty(this._label);
208-
209-
root.addEventListener('slotchange', () => {
210-
this._hideLabel = isEmpty(this._label);
211-
});
212-
return root;
213-
}
214-
215203
protected override async firstUpdated(): Promise<void> {
216204
await this.updateComplete;
217205

@@ -225,6 +213,10 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix
225213
}
226214
}
227215

216+
protected _handleSlotChange(): void {
217+
this._hideLabel = !this._slots.hasAssignedNodes('[default]', true);
218+
}
219+
228220
protected override _setDefaultValue(current: string | null): void {
229221
this._formValue.defaultValue = isDefined(current);
230222
for (const radio of this._siblings) {
@@ -346,6 +338,9 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix
346338

347339
protected override render() {
348340
const labelledBy = this.getAttribute('aria-labelledby');
341+
const describedBy = this._slots.hasAssignedElements('helper-text')
342+
? 'helper-text'
343+
: nothing;
349344
const checked = this.checked;
350345

351346
return html`
@@ -362,25 +357,24 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix
362357
type="radio"
363358
name=${ifDefined(this.name)}
364359
value=${ifDefined(this.value)}
365-
.required=${this.required}
366-
.disabled=${this.disabled}
360+
?required=${this.required}
361+
?disabled=${this.disabled}
367362
.checked=${live(checked)}
368363
tabindex=${this._tabIndex}
369-
aria-checked=${checked}
370-
aria-disabled=${this.disabled}
371364
aria-labelledby=${labelledBy ? labelledBy : this._labelId}
365+
aria-describedby=${describedBy}
372366
@click=${this._handleClick}
373367
/>
374368
<span part=${partMap({ control: true, checked })}>
375369
<span
376-
.hidden=${this.disabled}
377370
part=${partMap({ ripple: true, checked })}
371+
?hidden=${this.disabled}
378372
></span>
379373
</span>
380374
<span
381-
.hidden=${this._hideLabel}
382-
part=${partMap({ label: true, checked })}
383375
id=${this._labelId}
376+
part=${partMap({ label: true, checked })}
377+
?hidden=${this._hideLabel}
384378
>
385379
<slot></slot>
386380
</span>

0 commit comments

Comments
 (0)