This repository has been archived by the owner on Sep 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
radio-group.control.ts
201 lines (178 loc) · 5.6 KB
/
radio-group.control.ts
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import {
afterAll,
AfterEvent,
mapAfter,
OnEvent,
trackValue,
ValueTracker,
} from '@proc7ts/fun-events';
import { itsEach, itsEvery, overEntries } from '@proc7ts/push-iterator';
import { Supply } from '@proc7ts/supply';
import { InControl } from '../control';
import { InConverter } from '../converter';
/**
* Input control for a group of radio buttons.
*
* Groups several radio buttons. The value of this control is selected accordingly to the checked radio button.
*
* @category Control
* @typeParam TValue - Input value type. A `string` type that optionally accepting `undefined` values.
*/
export type InRadioGroup<TValue extends string | undefined = string | undefined> =
InControl<TValue>;
/**
* @category Control
*/
export namespace InRadioGroup {
/**
* Possible radio group control values corresponding to check states.
*
* @typeParam TValue - Radio button input value type.
*/
export interface Values<TValue extends string | undefined> {
/**
* Control value of radio group to use when none of the radio buttons is checked.
*/
readonly unchecked: TValue;
/**
* Input aspects applied by default.
*
* These are aspect converters to constructed control from the {@link inValueOf same-valued one}.
*/
readonly aspects?:
| InConverter.Aspect<TValue>
| readonly InConverter.Aspect<TValue>[]
| undefined;
}
/**
* Radio buttons map.
*
* A key is the radio group value used when corresponding radio button is checked. The value is a radio button, or
* an arbitrary input control that accepts at least `true` and `undefined` values. The latter means the radio button
* is not checked. Everything else means it is checked. To check the matching radio button the group would assign
* a `true` value to its control.
*
* @typeParam TValue - Radio buttons input value type.
*/
export type Buttons<TValue extends string | undefined> = {
readonly [value in Exclude<TValue, undefined>]?: InControl<true | undefined> | undefined;
};
}
/**
* @internal
*/
type RequiredInButtons<TValue extends string | undefined> = {
readonly [value in Exclude<TValue, undefined>]: InControl<true | undefined>;
};
/**
* @internal
*/
class InRadioGroupControl<TValue extends string | undefined> extends InControl<TValue> {
private readonly _unchecked: TValue;
private readonly _it: ValueTracker<TValue>;
constructor(
private readonly _buttons: RequiredInButtons<TValue>,
{ unchecked, aspects }: Partial<InRadioGroup.Values<TValue>> = {},
) {
super({ aspects });
this._unchecked = unchecked as TValue;
const read: AfterEvent<[TValue]> = afterAll(_buttons).do(
mapAfter(values => checkedInValue(values, unchecked) as TValue),
);
this._it = trackValue(unchecked as TValue).by(read);
this._it.on(value => {
itsEach(overEntries(_buttons), ([key, button]) => {
button.it = value === key || undefined;
});
});
}
get supply(): Supply {
return this._it.supply;
}
get it(): TValue {
return this._it.it;
}
set it(value: TValue) {
this._it.it =
value != null && this._buttons[value as keyof RequiredInButtons<TValue>]
? value
: this._unchecked;
}
get on(): OnEvent<[TValue, TValue]> {
return this._it.on;
}
}
/**
* @internal
*/
function checkedInValue<TValue extends string | undefined>(
values: { readonly [key in Exclude<TValue, undefined>]: [true | undefined] },
unchecked: TValue,
): TValue {
let checked: TValue = unchecked;
itsEvery(overEntries(values), ([key, [value]]) => {
if (value === undefined) {
return true;
}
checked = key;
return false;
});
return checked;
}
/**
* Creates a radio group for the given radio `buttons`.
*
* The created control has `undefined` value when none of the radio buttons is checked.
*
* @category Control
* @typeParam TValue - Input value type.
* @param buttons - Radio buttons map.
*
* @returns New radio group control instance.
*/
export function inRadioGroup<TValue extends string>(
buttons: InRadioGroup.Buttons<TValue>,
): InRadioGroup<TValue | undefined>;
/**
* Creates a radio group for the given radio `buttons` with default aspects.
*
* The created control has `undefined` value when none of the radio buttons is checked.
*
* @typeParam TValue - Input value type.
* @param buttons - Radio buttons map.
* @param aspects - Input aspects applied by default. These are aspect converters to constructed control
* from the {@link inValueOf same-valued one}.
*
* @returns New radio group control instance.
*/
export function inRadioGroup<TValue extends string>(
buttons: InRadioGroup.Buttons<TValue>,
// eslint-disable-next-line @typescript-eslint/unified-signatures
{
aspects,
}: {
readonly aspects?:
| InConverter.Aspect<TValue>
| readonly InConverter.Aspect<TValue>[]
| undefined;
},
): InRadioGroup<TValue | undefined>;
/**
* Creates a radio group for the given radio `buttons` with custom control `values`.
*
* @typeParam TValue - Input value type.
* @param buttons - Radio buttons map.
* @param values - Possible values of radio group control.
*
* @returns New radio group control instance.
*/
export function inRadioGroup<TValue extends string | undefined>(
buttons: InRadioGroup.Buttons<TValue>,
values: InRadioGroup.Values<TValue>,
): InRadioGroup<TValue>;
export function inRadioGroup<TValue extends string | undefined>(
buttons: InRadioGroup.Buttons<TValue>,
values?: Partial<InRadioGroup.Values<TValue>>,
): InRadioGroup<TValue> {
return new InRadioGroupControl(buttons as RequiredInButtons<TValue>, values);
}