/
radio-group.tsx
154 lines (132 loc) · 3.94 KB
/
radio-group.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, Watch, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { RadioGroupChangeEventDetail } from '../../interface';
import { findCheckedOption, watchForOptions } from '../../utils/watch-options';
@Component({
tag: 'ion-radio-group'
})
export class RadioGroup implements ComponentInterface {
private inputId = `ion-rg-${radioGroupIds++}`;
private labelId = `${this.inputId}-lbl`;
private mutationO?: MutationObserver;
@Element() el!: HTMLElement;
/**
* If `true`, the radios can be deselected.
*/
@Prop() allowEmptySelection = false;
/**
* The name of the control, which is submitted with the form data.
*/
@Prop() name: string = this.inputId;
/**
* the value of the radio group.
*/
@Prop({ mutable: true }) value?: any | null;
@Watch('value')
valueChanged(value: any | undefined) {
this.updateRadios();
this.ionChange.emit({ value });
}
/**
* Emitted when the value has changed.
*/
@Event() ionChange!: EventEmitter<RadioGroupChangeEventDetail>;
async connectedCallback() {
// Get the list header if it exists and set the id
// this is used to set aria-labelledby
const el = this.el;
const header = el.querySelector('ion-list-header') || el.querySelector('ion-item-divider');
if (header) {
const label = header.querySelector('ion-label');
if (label) {
this.labelId = label.id = this.name + '-lbl';
}
}
if (this.value === undefined) {
const radio = findCheckedOption(el, 'ion-radio') as HTMLIonRadioElement | undefined;
if (radio !== undefined) {
await radio.componentOnReady();
if (this.value === undefined) {
this.value = radio.value;
}
}
}
this.mutationO = watchForOptions<HTMLIonRadioElement>(el, 'ion-radio', newOption => {
if (newOption !== undefined) {
newOption.componentOnReady().then(() => {
this.value = newOption.value;
});
} else {
this.updateRadios();
}
});
this.updateRadios();
}
disconnectedCallback() {
if (this.mutationO) {
this.mutationO.disconnect();
this.mutationO = undefined;
}
}
private async updateRadios() {
/**
* Make sure we get all radios first
* so values are up to date prior
* to caching the radio group value
*/
const radios = await this.getRadios();
const { value } = this;
let hasChecked = false;
// Walk the DOM in reverse order, since the last selected one wins!
for (const radio of radios) {
if (!hasChecked && radio.value === value) {
// correct value for this radio
// but this radio isn't checked yet
// and we haven't found a checked yet
hasChecked = true;
radio.checked = true;
} else {
// this radio doesn't have the correct value
// or the radio group has been already checked
radio.checked = false;
}
}
// Reset value if
if (!hasChecked) {
this.value = undefined;
}
}
private getRadios() {
return Promise.all(
Array
.from(this.el.querySelectorAll('ion-radio'))
.map(r => r.componentOnReady())
);
}
private onSelect = (ev: Event) => {
const selectedRadio = ev.target as HTMLIonRadioElement | null;
if (selectedRadio) {
this.value = selectedRadio.value;
}
}
private onDeselect = (ev: Event) => {
const selectedRadio = ev.target as HTMLIonRadioElement | null;
if (selectedRadio) {
selectedRadio.checked = false;
this.value = undefined;
}
}
render() {
return (
<Host
role="radiogroup"
aria-labelledby={this.labelId}
onIonSelect={this.onSelect}
onIonDeselect={this.allowEmptySelection ? this.onDeselect : undefined}
class={getIonMode(this)}
>
</Host>
);
}
}
let radioGroupIds = 0;