Passed
Pull Request — master (#160)
by Wilmer
03:52 queued 01:20
created

RadioList::tabIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget;
6
7
use Closure;
8
use InvalidArgumentException;
9
use Stringable;
10
use Yiisoft\Form\Widget\Attribute\ChoiceAttributes;
11
use Yiisoft\Html\Widget\RadioList\RadioItem;
12
use Yiisoft\Html\Widget\RadioList\RadioList as RadioListTag;
13
14
/**
15
 * Generates a list of radio.
16
 */
17
final class RadioList extends ChoiceAttributes
18
{
19
    private array $containerAttributes = [];
20
    private ?string $containerTag = 'div';
21
    /** @psalm-var array[] */
22
    private array $individualItemsAttributes = [];
23
    /** @psalm-var array<array-key, string> */
24
    private array $items = [];
25
    private array $itemsAttributes = [];
26
    /** @psalm-var Closure(RadioItem):string|null */
27
    private ?Closure $itemsFormatter = null;
28
    /** @var bool[]|float[]|int[]|string[]|Stringable[] */
29
    private array $itemsFromValues = [];
30
    private string $separator = '';
31
    private ?string $uncheckValue = null;
32
33
    /**
34
     * Focus on the control (put cursor into it) when the page loads.
35
     * Only one form element could be in focus at the same time.
36
     *
37
     * @return static
38
     *
39
     * @link https://www.w3.org/TR/html52/sec-forms.html#autofocusing-a-form-control-the-autofocus-attribute
40
     *
41
     * @psalm-suppress MethodSignatureMismatch
42
     */
43 1
    public function autofocus(): self
44
    {
45 1
        $new = clone $this;
46 1
        $new->containerAttributes['autofocus'] = true;
47 1
        return $new;
48
    }
49
50
    /**
51
     * The container attributes for generating the list of checkboxes tag using {@see CheckBoxList}.
52
     *
53
     * @param array $value
54
     *
55
     * @return static
56
     *
57
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
58
     */
59 3
    public function containerAttributes(array $value): self
60
    {
61 3
        $new = clone $this;
62 3
        $new->containerAttributes = $value;
63 3
        return $new;
64
    }
65
66
    /**
67
     * The tag name for the container element.
68
     *
69
     * @param string|null $name tag name. if `null` disabled rendering.
70
     *
71
     * @return static
72
     */
73 5
    public function containerTag(?string $name = null): self
74
    {
75 5
        $new = clone $this;
76 5
        $new->containerTag = $name;
77 5
        return $new;
78
    }
79
80
    /**
81
     * Set the ID of container the widget.
82
     *
83
     * @param string|null $id
84
     *
85
     * @return static
86
     *
87
     * @link https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute
88
     *
89
     * @psalm-suppress MethodSignatureMismatch
90
     */
91 2
    public function id(?string $id): self
92
    {
93 2
        $new = clone $this;
94 2
        $new->containerAttributes['id'] = $id;
95 2
        return $new;
96
    }
97
98
    /**
99
     * The specified attributes for items.
100
     *
101
     * @param array $value
102
     *
103
     * @return static
104
     *
105
     * @psalm-param array[] $value
106
     */
107 3
    public function individualItemsAttributes(array $value = []): self
108
    {
109 3
        $new = clone $this;
110 3
        $new->individualItemsAttributes = $value;
111 3
        return $new;
112
    }
113
114
    /**
115
     * The data used to generate the list of radios.
116
     *
117
     * The array keys are the list of radio values, and the array values are the corresponding labels.
118
     *
119
     * Note that the labels will NOT be HTML-encoded, while the values will.
120
     *
121
     * @param array $value
122
     *
123
     * @return static
124
     *
125
     * @psalm-param array<array-key, string> $value
126
     */
127 36
    public function items(array $value = []): self
128
    {
129 36
        $new = clone $this;
130 36
        $new->items = $value;
131 36
        return $new;
132
    }
133
134
    /**
135
     * The attributes for generating the list of radio tag using {@see RadioList}.
136
     *
137
     * @param array $value
138
     *
139
     * @return static
140
     */
141 2
    public function itemsAttributes(array $value = []): self
142
    {
143 2
        $new = clone $this;
144 2
        $new->itemsAttributes = $value;
145 2
        return $new;
146
    }
147
148
    /**
149
     * Callable, a callback that can be used to customize the generation of the HTML code corresponding to a single
150
     * item in $items.
151
     *
152
     * The signature of this callback must be:
153
     *
154
     * ```php
155
     * function ($index, $label, $name, $checked, $value)
156
     * ```
157
     *
158
     * @param Closure|null $value
159
     *
160
     * @return static
161
     *
162
     * @psalm-param Closure(RadioItem):string|null $value
163
     */
164 3
    public function itemsFormatter(?Closure $value): self
165
    {
166 3
        $new = clone $this;
167 3
        $new->itemsFormatter = $value;
168 3
        return $new;
169
    }
170
171
    /**
172
     * The data used to generate the list of radios.
173
     *
174
     * The array keys are the list of radio values, and the array values are the corresponding labels.
175
     *
176
     * @param bool[]|float[]|int[]|string[]|Stringable[] $itemsFromValues
177
     *
178
     * @return static
179
     */
180 3
    public function itemsFromValues(array $itemsFromValues = []): self
181
    {
182 3
        $new = clone $this;
183 3
        $new->itemsFromValues = $itemsFromValues;
184 3
        return $new;
185
    }
186
187
    /**
188
     * The HTML code that separates items.
189
     *
190
     * @param string $value
191
     *
192
     * @return static
193
     */
194 3
    public function separator(string $value = ''): self
195
    {
196 3
        $new = clone $this;
197 3
        $new->separator = $value;
198 3
        return $new;
199
    }
200
201
    /**
202
     * The tabindex global attribute indicates that its element can be focused, and where it participates in sequential
203
     * keyboard navigation (usually with the Tab key, hence the name).
204
     *
205
     * It accepts an integer as a value, with different results depending on the integer's value:
206
     *
207
     * - A negative value (usually tabindex="-1") means that the element is not reachable via sequential keyboard
208
     * navigation, but could be focused with Javascript or visually. It's mostly useful to create accessible widgets
209
     * with JavaScript.
210
     * - tabindex="0" means that the element should be focusable in sequential keyboard navigation, but its order is
211
     * defined by the document's source order.
212
     * - A positive value means the element should be focusable in sequential keyboard navigation, with its order
213
     * defined by the value of the number. That is, tabindex="4" is focused before tabindex="5", but after tabindex="3".
214
     *
215
     * @param int $value
216
     *
217
     * @return static
218
     *
219
     * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
220
     *
221
     * @psalm-suppress MethodSignatureMismatch
222
     */
223 1
    public function tabIndex(int $value): self
224
    {
225 1
        $new = clone $this;
226 1
        $new->containerAttributes['tabindex'] = $value;
227 1
        return $new;
228
    }
229
230
    /**
231
     * @param bool|float|int|string|Stringable|null $value Value that corresponds to "unchecked" state of the input.
232
     *
233
     * @return static
234
     */
235 3
    public function uncheckValue($value): self
236
    {
237 3
        $new = clone $this;
238 3
        $new->uncheckValue = $value === null ? null : (string) $value;
239 3
        return $new;
240
    }
241
242
    /**
243
     * Generates a list of radio buttons.
244
     *
245
     * A radio button list is like a checkbox list, except that it only allows single selection.
246
     *
247
     * @return string the generated radio button list
248
     */
249 39
    protected function run(): string
250
    {
251
        /** @psalm-var array[] */
252 39
        [$attributes, $containerAttributes] = $this->buildList($this->attributes, $this->containerAttributes);
253
254
        /**
255
         * @var iterable<int, scalar|Stringable>|scalar|Stringable|null
256
         *
257
         * @link https://html.spec.whatwg.org/multipage/input.html#attr-input-value
258
         */
259 39
        $value = $attributes['value'] ?? $this->getAttributeValue();
260 39
        unset($attributes['value']);
261
262 39
        if (is_iterable($value) || is_object($value)) {
263 2
            throw new InvalidArgumentException('RadioList widget value can not be an iterable or an object.');
264
        }
265
266 37
        $name = $this->getInputName();
267
268
        /** @var string */
269 37
        if (!empty($attributes['name']) && is_string($attributes['name'])) {
270 2
            $name = $attributes['name'];
271
        }
272
273 37
        $radioList = RadioListTag::create($name);
274
275 37
        if ($this->items !== []) {
276 35
            $radioList = $radioList->items($this->items, $this->getEncode());
277 2
        } elseif ($this->itemsFromValues !== []) {
278 2
            $radioList = $radioList->itemsFromValues($this->itemsFromValues, $this->getEncode());
279
        }
280
281 37
        if ($this->separator !== '') {
282 2
            $radioList = $radioList->separator($this->separator);
283
        }
284
285 37
        if ($this->itemsAttributes !== []) {
286 1
            $radioList = $radioList->replaceRadioAttributes($this->itemsAttributes);
287
        }
288
289
        return $radioList
290 37
            ->containerAttributes($containerAttributes)
291 37
            ->containerTag($this->containerTag)
292 37
            ->individualInputAttributes($this->individualItemsAttributes)
293 37
            ->itemFormatter($this->itemsFormatter)
294 37
            ->radioAttributes($attributes)
295 37
            ->uncheckValue($this->uncheckValue)
296 37
            ->value(is_bool($value) ? (int) $value : $value)
297 37
            ->render();
298
    }
299
}
300