@justeattakeaway/pie-radio-group 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -59
- package/custom-elements.json +185 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +168 -76
- package/dist/react.d.ts +66 -0
- package/package.json +3 -3
- package/src/index.ts +157 -1
package/README.md
CHANGED
|
@@ -8,15 +8,6 @@
|
|
|
8
8
|
</a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
# Table of Contents
|
|
12
|
-
|
|
13
|
-
1. [Introduction](#pie-radio-group)
|
|
14
|
-
2. [Installation](#installation)
|
|
15
|
-
3. [Importing the component](#importing-the-component)
|
|
16
|
-
4. [Peer Dependencies](#peer-dependencies)
|
|
17
|
-
5. [Props](#props)
|
|
18
|
-
6. [Contributing](#contributing)
|
|
19
|
-
|
|
20
11
|
## pie-radio-group
|
|
21
12
|
|
|
22
13
|
`pie-radio-group` is a Web Component built using the Lit library.
|
|
@@ -29,63 +20,22 @@ This component can be easily integrated into various frontend frameworks and cus
|
|
|
29
20
|
To install `pie-radio-group` in your application, run the following on your command line:
|
|
30
21
|
|
|
31
22
|
```bash
|
|
32
|
-
|
|
33
|
-
$ npm i @justeattakeaway/pie-radio-group
|
|
34
|
-
|
|
35
|
-
# yarn
|
|
36
|
-
$ yarn add @justeattakeaway/pie-radio-group
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
For full information on using PIE components as part of an application, check out the [Getting Started Guide](https://github.com/justeattakeaway/pie/wiki/Getting-started-with-PIE-Web-Components).
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
### Importing the component
|
|
43
|
-
|
|
44
|
-
#### JavaScript
|
|
45
|
-
```js
|
|
46
|
-
// Default – for Native JS Applications, Vue, Angular, Svelte, etc.
|
|
47
|
-
import { PieRadioGroup } from '@justeattakeaway/pie-radio-group';
|
|
48
|
-
|
|
49
|
-
// If you don't need to reference the imported object, you can simply
|
|
50
|
-
// import the module which registers the component as a custom element.
|
|
51
|
-
import '@justeattakeaway/pie-radio-group';
|
|
23
|
+
npm i @justeattakeaway/pie-radio-group
|
|
52
24
|
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
// React
|
|
57
|
-
// For React, you will need to import our React-specific component build
|
|
58
|
-
// which wraps the web component using @lit/react
|
|
59
|
-
import { PieRadioGroup } from '@justeattakeaway/pie-radio-group/dist/react';
|
|
25
|
+
```bash
|
|
26
|
+
yarn add @justeattakeaway/pie-radio-group
|
|
60
27
|
```
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
> When using the React version of the component, please make sure to also
|
|
64
|
-
> include React as a [peer dependency](#peer-dependencies) in your project.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
## Peer Dependencies
|
|
68
|
-
|
|
69
|
-
> [!IMPORTANT]
|
|
70
|
-
> When using `pie-radio-group`, you will also need to include a couple of dependencies to ensure the component renders as expected. See [the PIE Wiki](https://github.com/justeattakeaway/pie/wiki/Getting-started-with-PIE-Web-Components#expected-dependencies) for more information and how to include these in your application.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
## Props
|
|
29
|
+
For full information on using PIE components as part of an application, check out the [Getting Started Guide](https://github.com/justeattakeaway/pie/wiki/Getting-started-with-PIE-Web-Components).
|
|
74
30
|
|
|
75
|
-
|
|
76
|
-
|---|---|---|---|
|
|
77
|
-
| - | - | - | - |
|
|
31
|
+
## Documentation
|
|
78
32
|
|
|
79
|
-
|
|
33
|
+
Visit [Radio Group | PIE Design System](https://pie.design/components/radio-group/code) to view more information on this component.
|
|
80
34
|
|
|
81
|
-
|
|
82
|
-
<!-- Native HTML -->
|
|
83
|
-
<pie-radio-group></pie-radio-group>
|
|
35
|
+
## Questions
|
|
84
36
|
|
|
85
|
-
|
|
86
|
-
<PieRadioGroup></PieRadioGroup>
|
|
87
|
-
```
|
|
37
|
+
Please head to [FAQs | PIE Design System](https://pie.design/support/contact-us/) to see our FAQs and get in touch.
|
|
88
38
|
|
|
89
39
|
## Contributing
|
|
90
40
|
|
|
91
|
-
Check out our [contributing guide](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide) for more information on [local development](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#local-development) and how to run specific [component tests](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#testing).
|
|
41
|
+
Check out our [contributing guide](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide) for more information on [local development](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#local-development) and how to run specific [component tests](https://github.com/justeattakeaway/pie/wiki/Contributing-Guide#testing).
|
package/custom-elements.json
CHANGED
|
@@ -143,6 +143,14 @@
|
|
|
143
143
|
"text": "Array<HTMLInputElement>"
|
|
144
144
|
}
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
"kind": "field",
|
|
148
|
+
"name": "_fieldset",
|
|
149
|
+
"type": {
|
|
150
|
+
"text": "HTMLInputElement"
|
|
151
|
+
},
|
|
152
|
+
"privacy": "private"
|
|
153
|
+
},
|
|
146
154
|
{
|
|
147
155
|
"kind": "field",
|
|
148
156
|
"name": "_abortController",
|
|
@@ -151,6 +159,17 @@
|
|
|
151
159
|
},
|
|
152
160
|
"privacy": "private"
|
|
153
161
|
},
|
|
162
|
+
{
|
|
163
|
+
"kind": "field",
|
|
164
|
+
"name": "_wasShiftTabPressed",
|
|
165
|
+
"type": {
|
|
166
|
+
"text": "boolean"
|
|
167
|
+
},
|
|
168
|
+
"privacy": "private",
|
|
169
|
+
"static": true,
|
|
170
|
+
"default": "false",
|
|
171
|
+
"description": "Tracks whether the `Shift` key was held during the last `Tab` key press.\n\nThe property is static because it needs to be shared across all instances of the\n`PieRadioGroup` component on the same page, ensuring consistent behavior."
|
|
172
|
+
},
|
|
154
173
|
{
|
|
155
174
|
"kind": "method",
|
|
156
175
|
"name": "_handleDisabled",
|
|
@@ -243,6 +262,172 @@
|
|
|
243
262
|
}
|
|
244
263
|
},
|
|
245
264
|
"description": "Renders the label element inside a legend, wrapping the slot content."
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"kind": "method",
|
|
268
|
+
"name": "_updateShiftTabState",
|
|
269
|
+
"privacy": "private",
|
|
270
|
+
"return": {
|
|
271
|
+
"type": {
|
|
272
|
+
"text": "void"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"parameters": [
|
|
276
|
+
{
|
|
277
|
+
"name": "event",
|
|
278
|
+
"type": {
|
|
279
|
+
"text": "KeyboardEvent"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"description": "Updates the state of `_wasShiftTabPressed` based on the last `Tab` key press."
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"kind": "method",
|
|
287
|
+
"name": "_handleFocusIn",
|
|
288
|
+
"privacy": "private",
|
|
289
|
+
"return": {
|
|
290
|
+
"type": {
|
|
291
|
+
"text": "void"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
"parameters": [
|
|
295
|
+
{
|
|
296
|
+
"name": "event",
|
|
297
|
+
"type": {
|
|
298
|
+
"text": "FocusEvent"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
"description": "Handles the `focusin` event to manage focus within the radio group.\n\nThis method determines the appropriate element to focus when the radio group\ngains focus. It considers the last navigation action (whether `Shift+Tab` was used)\nand focuses the checked option, the first option, or the last option as needed."
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"kind": "method",
|
|
306
|
+
"name": "_handleFocusOut",
|
|
307
|
+
"privacy": "private",
|
|
308
|
+
"return": {
|
|
309
|
+
"type": {
|
|
310
|
+
"text": "void"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"description": "Handles the `focusout` event to restore the `tabindex` on the radio group's `fieldset`.\n\nWhen focus leaves the radio group, this method enables the `tabindex` attribute\non the `fieldset` element. This ensures the radio group remains accessible for\nkeyboard navigation and can be re-focused when tabbing back into the group."
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"kind": "method",
|
|
317
|
+
"name": "_toggleFieldsetTabindex",
|
|
318
|
+
"privacy": "private",
|
|
319
|
+
"return": {
|
|
320
|
+
"type": {
|
|
321
|
+
"text": "void"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"parameters": [
|
|
325
|
+
{
|
|
326
|
+
"name": "enable",
|
|
327
|
+
"type": {
|
|
328
|
+
"text": "boolean"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"kind": "method",
|
|
335
|
+
"name": "_moveFocus",
|
|
336
|
+
"privacy": "private",
|
|
337
|
+
"return": {
|
|
338
|
+
"type": {
|
|
339
|
+
"text": "void"
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"parameters": [
|
|
343
|
+
{
|
|
344
|
+
"name": "currentIndex",
|
|
345
|
+
"type": {
|
|
346
|
+
"text": "number"
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"name": "step",
|
|
351
|
+
"type": {
|
|
352
|
+
"text": "number"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"kind": "method",
|
|
359
|
+
"name": "_isForwardKey",
|
|
360
|
+
"privacy": "private",
|
|
361
|
+
"return": {
|
|
362
|
+
"type": {
|
|
363
|
+
"text": "boolean"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"parameters": [
|
|
367
|
+
{
|
|
368
|
+
"name": "event",
|
|
369
|
+
"type": {
|
|
370
|
+
"text": "KeyboardEvent"
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
],
|
|
374
|
+
"description": "Determines if a key press indicates forward navigation within the radio group.\n\nThis method evaluates a keyboard event to check if the pressed key corresponds\nto forward navigation based on the current text direction (LTR or RTL).\n\n**Behaviour:**\n- For LTR (Left-to-Right) layouts:\n - `ArrowRight` and `ArrowDown` indicate forward navigation.\n- For RTL (Right-to-Left) layouts:\n - `ArrowLeft` and `ArrowDown` indicate forward navigation."
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"kind": "method",
|
|
378
|
+
"name": "_isBackwardKey",
|
|
379
|
+
"privacy": "private",
|
|
380
|
+
"return": {
|
|
381
|
+
"type": {
|
|
382
|
+
"text": "boolean"
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"parameters": [
|
|
386
|
+
{
|
|
387
|
+
"name": "event",
|
|
388
|
+
"type": {
|
|
389
|
+
"text": "KeyboardEvent"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
],
|
|
393
|
+
"description": "Determines if a key press indicates backward navigation within the radio group.\n\nThis method evaluates a keyboard event to check if the pressed key corresponds\nto backward navigation based on the current text direction (LTR or RTL).\n\n**Behaviour:**\n- For LTR (Left-to-Right) layouts:\n - `ArrowLeft` and `ArrowUp` indicate backward navigation.\n- For RTL (Right-to-Left) layouts:\n - `ArrowRight` and `ArrowUp` indicate backward navigation."
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"kind": "method",
|
|
397
|
+
"name": "_handleKeyDown",
|
|
398
|
+
"privacy": "private",
|
|
399
|
+
"return": {
|
|
400
|
+
"type": {
|
|
401
|
+
"text": "void"
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
"parameters": [
|
|
405
|
+
{
|
|
406
|
+
"name": "event",
|
|
407
|
+
"type": {
|
|
408
|
+
"text": "KeyboardEvent"
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
],
|
|
412
|
+
"description": "Handles keyboard navigation within the radio group using arrow keys.\n\nThis method responds to `keydown` events and determines the appropriate navigation\naction (forward or backward) based on the pressed key and the current focus. It prevents\nthe default browser behaviour (e.g., scrolling) when arrow keys are used for navigation."
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"kind": "method",
|
|
416
|
+
"name": "_focusAndClickOption",
|
|
417
|
+
"privacy": "private",
|
|
418
|
+
"return": {
|
|
419
|
+
"type": {
|
|
420
|
+
"text": "void"
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
"parameters": [
|
|
424
|
+
{
|
|
425
|
+
"name": "option",
|
|
426
|
+
"type": {
|
|
427
|
+
"text": "HTMLInputElement"
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
]
|
|
246
431
|
}
|
|
247
432
|
],
|
|
248
433
|
"events": [
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,15 @@ export declare class PieRadioGroup extends PieRadioGroup_base implements RadioGr
|
|
|
33
33
|
assistiveText?: RadioGroupProps['assistiveText'];
|
|
34
34
|
status: "default" | "error" | "success";
|
|
35
35
|
_slottedChildren: Array<HTMLInputElement>;
|
|
36
|
+
private _fieldset;
|
|
36
37
|
private _abortController;
|
|
38
|
+
/**
|
|
39
|
+
* Tracks whether the `Shift` key was held during the last `Tab` key press.
|
|
40
|
+
*
|
|
41
|
+
* The property is static because it needs to be shared across all instances of the
|
|
42
|
+
* `PieRadioGroup` component on the same page, ensuring consistent behavior.
|
|
43
|
+
*/
|
|
44
|
+
private static _wasShiftTabPressed;
|
|
37
45
|
/**
|
|
38
46
|
* Dispatches a custom event to notify each slotted child radio element
|
|
39
47
|
* when the radio group is disabled.
|
|
@@ -71,7 +79,65 @@ export declare class PieRadioGroup extends PieRadioGroup_base implements RadioGr
|
|
|
71
79
|
*/
|
|
72
80
|
private _renderWrappedLabel;
|
|
73
81
|
protected updated(_changedProperties: PropertyValues<this>): void;
|
|
82
|
+
protected firstUpdated(): void;
|
|
74
83
|
connectedCallback(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Updates the state of `_wasShiftTabPressed` based on the last `Tab` key press.
|
|
86
|
+
*/
|
|
87
|
+
private _updateShiftTabState;
|
|
88
|
+
/**
|
|
89
|
+
* Handles the `focusin` event to manage focus within the radio group.
|
|
90
|
+
*
|
|
91
|
+
* This method determines the appropriate element to focus when the radio group
|
|
92
|
+
* gains focus. It considers the last navigation action (whether `Shift+Tab` was used)
|
|
93
|
+
* and focuses the checked option, the first option, or the last option as needed.
|
|
94
|
+
*/
|
|
95
|
+
private _handleFocusIn;
|
|
96
|
+
/**
|
|
97
|
+
* Handles the `focusout` event to restore the `tabindex` on the radio group's `fieldset`.
|
|
98
|
+
*
|
|
99
|
+
* When focus leaves the radio group, this method enables the `tabindex` attribute
|
|
100
|
+
* on the `fieldset` element. This ensures the radio group remains accessible for
|
|
101
|
+
* keyboard navigation and can be re-focused when tabbing back into the group.
|
|
102
|
+
*/
|
|
103
|
+
private _handleFocusOut;
|
|
104
|
+
private _toggleFieldsetTabindex;
|
|
105
|
+
private _moveFocus;
|
|
106
|
+
/**
|
|
107
|
+
* Determines if a key press indicates forward navigation within the radio group.
|
|
108
|
+
*
|
|
109
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
110
|
+
* to forward navigation based on the current text direction (LTR or RTL).
|
|
111
|
+
*
|
|
112
|
+
* **Behaviour:**
|
|
113
|
+
* - For LTR (Left-to-Right) layouts:
|
|
114
|
+
* - `ArrowRight` and `ArrowDown` indicate forward navigation.
|
|
115
|
+
* - For RTL (Right-to-Left) layouts:
|
|
116
|
+
* - `ArrowLeft` and `ArrowDown` indicate forward navigation.
|
|
117
|
+
*/
|
|
118
|
+
private _isForwardKey;
|
|
119
|
+
/**
|
|
120
|
+
* Determines if a key press indicates backward navigation within the radio group.
|
|
121
|
+
*
|
|
122
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
123
|
+
* to backward navigation based on the current text direction (LTR or RTL).
|
|
124
|
+
*
|
|
125
|
+
* **Behaviour:**
|
|
126
|
+
* - For LTR (Left-to-Right) layouts:
|
|
127
|
+
* - `ArrowLeft` and `ArrowUp` indicate backward navigation.
|
|
128
|
+
* - For RTL (Right-to-Left) layouts:
|
|
129
|
+
* - `ArrowRight` and `ArrowUp` indicate backward navigation.
|
|
130
|
+
*/
|
|
131
|
+
private _isBackwardKey;
|
|
132
|
+
/**
|
|
133
|
+
* Handles keyboard navigation within the radio group using arrow keys.
|
|
134
|
+
*
|
|
135
|
+
* This method responds to `keydown` events and determines the appropriate navigation
|
|
136
|
+
* action (forward or backward) based on the pressed key and the current focus. It prevents
|
|
137
|
+
* the default browser behaviour (e.g., scrolling) when arrow keys are used for navigation.
|
|
138
|
+
*/
|
|
139
|
+
private _handleKeyDown;
|
|
140
|
+
private _focusAndClickOption;
|
|
75
141
|
disconnectedCallback(): void;
|
|
76
142
|
render(): TemplateResult<1>;
|
|
77
143
|
static styles: CSSResult;
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import { LitElement as
|
|
2
|
-
import { state as
|
|
3
|
-
import { FormControlMixin as C, RtlMixin as x, wrapNativeEvent as S, validPropertyValues as
|
|
4
|
-
import { ifDefined as
|
|
5
|
-
import { classMap as
|
|
1
|
+
import { LitElement as g, html as c, nothing as p, unsafeCSS as m } from "lit";
|
|
2
|
+
import { state as v, property as l, queryAssignedElements as y, query as w } from "lit/decorators.js";
|
|
3
|
+
import { FormControlMixin as C, RtlMixin as x, wrapNativeEvent as S, validPropertyValues as A, defineCustomElement as T } from "@justeattakeaway/pie-webc-core";
|
|
4
|
+
import { ifDefined as L } from "lit/directives/if-defined.js";
|
|
5
|
+
import { classMap as E } from "lit/directives/class-map.js";
|
|
6
6
|
import "@justeattakeaway/pie-assistive-text";
|
|
7
|
-
const
|
|
7
|
+
const R = "*,*:after,*:before{box-sizing:inherit}.c-radioGroup{--radio-group-gap: var(--dt-spacing-c);--radio-group-gap--inline: var(--dt-spacing-c) var(--dt-spacing-e);margin:0;padding:0;border:0;min-width:0;display:flex;flex-flow:column wrap;gap:var(--radio-group-gap)}.c-radioGroup.c-radioGroup--inline{flex-flow:row wrap;gap:var(--radio-group-gap--inline)}.c-radioGroup.c-radioGroup--hasAssistiveText{margin-block-end:var(--dt-spacing-a)}", k = ["default", "success", "error"], F = "pie-radio-group-disabled", h = {
|
|
8
8
|
status: "default",
|
|
9
9
|
disabled: !1,
|
|
10
10
|
isInline: !1,
|
|
11
11
|
value: ""
|
|
12
12
|
};
|
|
13
|
-
var
|
|
14
|
-
for (var
|
|
15
|
-
(
|
|
16
|
-
return
|
|
13
|
+
var I = Object.defineProperty, d = (u, t, e, s) => {
|
|
14
|
+
for (var i = void 0, a = u.length - 1, n; a >= 0; a--)
|
|
15
|
+
(n = u[a]) && (i = n(t, e, i) || i);
|
|
16
|
+
return i && I(t, e, i), i;
|
|
17
17
|
};
|
|
18
|
-
const
|
|
18
|
+
const _ = "pie-radio-group", f = "assistive-text";
|
|
19
|
+
var r;
|
|
20
|
+
const o = (r = class extends C(x(g)) {
|
|
19
21
|
constructor() {
|
|
20
|
-
super(...arguments), this._hasLabel = !1, this.value =
|
|
22
|
+
super(...arguments), this._hasLabel = !1, this.value = h.value, this.isInline = h.isInline, this.disabled = h.disabled, this.status = h.status;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Dispatches a custom event to notify each slotted child radio element
|
|
@@ -25,7 +27,7 @@ const b = "pie-radio-group", g = "assistive-text", h = class h extends C(x(v)) {
|
|
|
25
27
|
* @private
|
|
26
28
|
*/
|
|
27
29
|
_handleDisabled() {
|
|
28
|
-
this._slottedChildren.forEach((
|
|
30
|
+
this._slottedChildren.forEach((t) => t.dispatchEvent(new CustomEvent(F, {
|
|
29
31
|
bubbles: !1,
|
|
30
32
|
composed: !1,
|
|
31
33
|
detail: { disabled: this.disabled }
|
|
@@ -37,16 +39,16 @@ const b = "pie-radio-group", g = "assistive-text", h = class h extends C(x(v)) {
|
|
|
37
39
|
* @returns {void}
|
|
38
40
|
*/
|
|
39
41
|
_handleStatus() {
|
|
40
|
-
this._slottedChildren.forEach((
|
|
42
|
+
this._slottedChildren.forEach((t) => t.setAttribute("status", this.status === "error" ? "error" : "default"));
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Unselects all radios that are not the selected value.
|
|
44
46
|
* @param {string} selectedValue - The value of the currently selected radio.
|
|
45
47
|
* @private
|
|
46
48
|
*/
|
|
47
|
-
_handleRadioSelection(
|
|
48
|
-
this.value =
|
|
49
|
-
|
|
49
|
+
_handleRadioSelection(t) {
|
|
50
|
+
this.value = t, this._slottedChildren.forEach((e) => {
|
|
51
|
+
e.disabled || (e.checked = e.value === t);
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
@@ -54,21 +56,21 @@ const b = "pie-radio-group", g = "assistive-text", h = class h extends C(x(v)) {
|
|
|
54
56
|
* @param {Event} event - The change event from a radio element.
|
|
55
57
|
* @private
|
|
56
58
|
*/
|
|
57
|
-
_handleRadioChange(
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
this._handleRadioSelection(
|
|
61
|
-
const
|
|
62
|
-
this.dispatchEvent(
|
|
59
|
+
_handleRadioChange(t) {
|
|
60
|
+
t.stopPropagation();
|
|
61
|
+
const e = t.target;
|
|
62
|
+
this._handleRadioSelection(e.value);
|
|
63
|
+
const s = S(t);
|
|
64
|
+
this.dispatchEvent(s);
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Updates the `_hasLabel` state when content is added to the label slot.
|
|
66
68
|
* @param {Event} e - The slotchange event.
|
|
67
69
|
* @private
|
|
68
70
|
*/
|
|
69
|
-
_handleSlotChange(
|
|
70
|
-
const
|
|
71
|
-
this._hasLabel =
|
|
71
|
+
_handleSlotChange(t) {
|
|
72
|
+
const e = t.target.assignedNodes({ flatten: !0 });
|
|
73
|
+
this._hasLabel = e.length > 0;
|
|
72
74
|
}
|
|
73
75
|
/**
|
|
74
76
|
* Renders the label element inside a legend, wrapping the slot content.
|
|
@@ -76,83 +78,173 @@ const b = "pie-radio-group", g = "assistive-text", h = class h extends C(x(v)) {
|
|
|
76
78
|
* @private
|
|
77
79
|
*/
|
|
78
80
|
_renderWrappedLabel() {
|
|
79
|
-
return this._hasLabel ?
|
|
81
|
+
return this._hasLabel ? c`<legend><slot name='label' @slotchange=${this._handleSlotChange}></slot></legend>` : c`<slot name='label' @slotchange=${this._handleSlotChange}></slot>`;
|
|
80
82
|
}
|
|
81
|
-
updated(
|
|
82
|
-
|
|
83
|
+
updated(t) {
|
|
84
|
+
t.has("disabled") && this._handleDisabled(), t.has("value") && this._handleRadioSelection(this.value), t.has("status") && this._handleStatus();
|
|
85
|
+
}
|
|
86
|
+
firstUpdated() {
|
|
87
|
+
this._slottedChildren.forEach((t) => t.setAttribute("tabindex", "-1"));
|
|
83
88
|
}
|
|
84
89
|
connectedCallback() {
|
|
85
|
-
var
|
|
90
|
+
var e;
|
|
86
91
|
super.connectedCallback(), this._abortController = new AbortController();
|
|
87
|
-
const { signal:
|
|
88
|
-
(
|
|
92
|
+
const { signal: t } = this._abortController;
|
|
93
|
+
(e = this.shadowRoot) == null || e.addEventListener("change", this._handleRadioChange.bind(this), { signal: t }), this.addEventListener("focusin", this._handleFocusIn, { signal: t }), this.addEventListener("focusout", this._handleFocusOut, { signal: t }), this.addEventListener("keydown", this._handleKeyDown, { signal: t }), document.addEventListener("keydown", this._updateShiftTabState.bind(this), { signal: t });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Updates the state of `_wasShiftTabPressed` based on the last `Tab` key press.
|
|
97
|
+
*/
|
|
98
|
+
_updateShiftTabState(t) {
|
|
99
|
+
t.key === "Tab" && (r._wasShiftTabPressed = t.shiftKey);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Handles the `focusin` event to manage focus within the radio group.
|
|
103
|
+
*
|
|
104
|
+
* This method determines the appropriate element to focus when the radio group
|
|
105
|
+
* gains focus. It considers the last navigation action (whether `Shift+Tab` was used)
|
|
106
|
+
* and focuses the checked option, the first option, or the last option as needed.
|
|
107
|
+
*/
|
|
108
|
+
_handleFocusIn(t) {
|
|
109
|
+
var i;
|
|
110
|
+
if (this !== t.target) return;
|
|
111
|
+
const e = r._wasShiftTabPressed, s = ((i = this._slottedChildren) == null ? void 0 : i.find((a) => a.checked)) || (e ? this._slottedChildren.at(-1) : this._slottedChildren[0]);
|
|
112
|
+
s && (s.focus(), this._toggleFieldsetTabindex(!1));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Handles the `focusout` event to restore the `tabindex` on the radio group's `fieldset`.
|
|
116
|
+
*
|
|
117
|
+
* When focus leaves the radio group, this method enables the `tabindex` attribute
|
|
118
|
+
* on the `fieldset` element. This ensures the radio group remains accessible for
|
|
119
|
+
* keyboard navigation and can be re-focused when tabbing back into the group.
|
|
120
|
+
*/
|
|
121
|
+
_handleFocusOut() {
|
|
122
|
+
this._toggleFieldsetTabindex(!0);
|
|
123
|
+
}
|
|
124
|
+
_toggleFieldsetTabindex(t) {
|
|
125
|
+
t ? this._fieldset.setAttribute("tabindex", "0") : this._fieldset.removeAttribute("tabindex");
|
|
126
|
+
}
|
|
127
|
+
_moveFocus(t, e) {
|
|
128
|
+
const s = (t + e + this._slottedChildren.length) % this._slottedChildren.length;
|
|
129
|
+
this._focusAndClickOption(this._slottedChildren[s]);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Determines if a key press indicates forward navigation within the radio group.
|
|
133
|
+
*
|
|
134
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
135
|
+
* to forward navigation based on the current text direction (LTR or RTL).
|
|
136
|
+
*
|
|
137
|
+
* **Behaviour:**
|
|
138
|
+
* - For LTR (Left-to-Right) layouts:
|
|
139
|
+
* - `ArrowRight` and `ArrowDown` indicate forward navigation.
|
|
140
|
+
* - For RTL (Right-to-Left) layouts:
|
|
141
|
+
* - `ArrowLeft` and `ArrowDown` indicate forward navigation.
|
|
142
|
+
*/
|
|
143
|
+
_isForwardKey(t) {
|
|
144
|
+
return t.code === "ArrowRight" && !this.isRTL || t.code === "ArrowLeft" && this.isRTL || t.code === "ArrowDown";
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Determines if a key press indicates backward navigation within the radio group.
|
|
148
|
+
*
|
|
149
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
150
|
+
* to backward navigation based on the current text direction (LTR or RTL).
|
|
151
|
+
*
|
|
152
|
+
* **Behaviour:**
|
|
153
|
+
* - For LTR (Left-to-Right) layouts:
|
|
154
|
+
* - `ArrowLeft` and `ArrowUp` indicate backward navigation.
|
|
155
|
+
* - For RTL (Right-to-Left) layouts:
|
|
156
|
+
* - `ArrowRight` and `ArrowUp` indicate backward navigation.
|
|
157
|
+
*/
|
|
158
|
+
_isBackwardKey(t) {
|
|
159
|
+
return t.code === "ArrowLeft" && !this.isRTL || t.code === "ArrowRight" && this.isRTL || t.code === "ArrowUp";
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Handles keyboard navigation within the radio group using arrow keys.
|
|
163
|
+
*
|
|
164
|
+
* This method responds to `keydown` events and determines the appropriate navigation
|
|
165
|
+
* action (forward or backward) based on the pressed key and the current focus. It prevents
|
|
166
|
+
* the default browser behaviour (e.g., scrolling) when arrow keys are used for navigation.
|
|
167
|
+
*/
|
|
168
|
+
_handleKeyDown(t) {
|
|
169
|
+
const e = this._slottedChildren.find((i) => i === document.activeElement);
|
|
170
|
+
if (!e)
|
|
171
|
+
return;
|
|
172
|
+
const s = this._slottedChildren.indexOf(e);
|
|
173
|
+
s !== -1 && (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(t.code) && t.preventDefault(), this._isForwardKey(t) ? this._moveFocus(s, 1) : this._isBackwardKey(t) && this._moveFocus(s, -1));
|
|
174
|
+
}
|
|
175
|
+
_focusAndClickOption(t) {
|
|
176
|
+
var e, s;
|
|
177
|
+
t.focus(), (s = (e = t.shadowRoot) == null ? void 0 : e.querySelector("input")) == null || s.click(), this._toggleFieldsetTabindex(!1);
|
|
89
178
|
}
|
|
90
179
|
disconnectedCallback() {
|
|
91
180
|
super.disconnectedCallback(), this._abortController.abort();
|
|
92
181
|
}
|
|
93
182
|
render() {
|
|
94
183
|
const {
|
|
95
|
-
name:
|
|
96
|
-
isInline:
|
|
97
|
-
disabled:
|
|
98
|
-
status:
|
|
99
|
-
assistiveText:
|
|
100
|
-
} = this,
|
|
184
|
+
name: t,
|
|
185
|
+
isInline: e,
|
|
186
|
+
disabled: s,
|
|
187
|
+
status: i,
|
|
188
|
+
assistiveText: a
|
|
189
|
+
} = this, n = !!(a != null && a.length), b = {
|
|
101
190
|
"c-radioGroup": !0,
|
|
102
|
-
"c-radioGroup--inline":
|
|
103
|
-
"c-radioGroup--hasAssistiveText":
|
|
191
|
+
"c-radioGroup--inline": e,
|
|
192
|
+
"c-radioGroup--hasAssistiveText": n
|
|
104
193
|
};
|
|
105
|
-
return
|
|
194
|
+
return c`
|
|
106
195
|
<fieldset
|
|
107
|
-
|
|
108
|
-
|
|
196
|
+
tabindex="0"
|
|
197
|
+
name=${L(t)}
|
|
198
|
+
?disabled=${s}
|
|
109
199
|
data-test-id="pie-radio-group"
|
|
110
|
-
aria-describedby=${
|
|
111
|
-
class="${
|
|
200
|
+
aria-describedby=${n ? f : p}
|
|
201
|
+
class="${E(b)}">
|
|
112
202
|
${this._renderWrappedLabel()}
|
|
113
203
|
<slot></slot>
|
|
114
204
|
</fieldset>
|
|
115
|
-
${
|
|
205
|
+
${n ? c`
|
|
116
206
|
<pie-assistive-text
|
|
117
|
-
id=${
|
|
118
|
-
variant=${
|
|
207
|
+
id=${f}
|
|
208
|
+
variant=${i}
|
|
119
209
|
data-test-id="pie-radio-group-assistive-text">
|
|
120
|
-
${
|
|
121
|
-
</pie-assistive-text>` :
|
|
210
|
+
${a}
|
|
211
|
+
</pie-assistive-text>` : p}
|
|
122
212
|
`;
|
|
123
213
|
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
o
|
|
128
|
-
|
|
129
|
-
], s.prototype, "_hasLabel");
|
|
130
|
-
o([
|
|
214
|
+
}, r._wasShiftTabPressed = !1, r.styles = m(R), r);
|
|
215
|
+
d([
|
|
216
|
+
v()
|
|
217
|
+
], o.prototype, "_hasLabel");
|
|
218
|
+
d([
|
|
131
219
|
l({ type: String })
|
|
132
|
-
],
|
|
133
|
-
|
|
220
|
+
], o.prototype, "name");
|
|
221
|
+
d([
|
|
134
222
|
l({ type: String })
|
|
135
|
-
],
|
|
136
|
-
|
|
223
|
+
], o.prototype, "value");
|
|
224
|
+
d([
|
|
137
225
|
l({ type: Boolean })
|
|
138
|
-
],
|
|
139
|
-
|
|
226
|
+
], o.prototype, "isInline");
|
|
227
|
+
d([
|
|
140
228
|
l({ type: Boolean, reflect: !0 })
|
|
141
|
-
],
|
|
142
|
-
|
|
229
|
+
], o.prototype, "disabled");
|
|
230
|
+
d([
|
|
143
231
|
l({ type: String })
|
|
144
|
-
],
|
|
145
|
-
|
|
232
|
+
], o.prototype, "assistiveText");
|
|
233
|
+
d([
|
|
146
234
|
l({ type: String }),
|
|
147
|
-
|
|
148
|
-
],
|
|
149
|
-
|
|
235
|
+
A(_, k, h.status)
|
|
236
|
+
], o.prototype, "status");
|
|
237
|
+
d([
|
|
150
238
|
y({ selector: "pie-radio" })
|
|
151
|
-
],
|
|
152
|
-
|
|
239
|
+
], o.prototype, "_slottedChildren");
|
|
240
|
+
d([
|
|
241
|
+
w("fieldset")
|
|
242
|
+
], o.prototype, "_fieldset");
|
|
243
|
+
let D = o;
|
|
244
|
+
T(_, D);
|
|
153
245
|
export {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
246
|
+
F as ON_RADIO_GROUP_DISABLED,
|
|
247
|
+
D as PieRadioGroup,
|
|
248
|
+
h as defaultProps,
|
|
249
|
+
k as statusTypes
|
|
158
250
|
};
|
package/dist/react.d.ts
CHANGED
|
@@ -36,7 +36,15 @@ declare class PieRadioGroup_2 extends PieRadioGroup_base implements RadioGroupPr
|
|
|
36
36
|
assistiveText?: RadioGroupProps['assistiveText'];
|
|
37
37
|
status: "default" | "error" | "success";
|
|
38
38
|
_slottedChildren: Array<HTMLInputElement>;
|
|
39
|
+
private _fieldset;
|
|
39
40
|
private _abortController;
|
|
41
|
+
/**
|
|
42
|
+
* Tracks whether the `Shift` key was held during the last `Tab` key press.
|
|
43
|
+
*
|
|
44
|
+
* The property is static because it needs to be shared across all instances of the
|
|
45
|
+
* `PieRadioGroup` component on the same page, ensuring consistent behavior.
|
|
46
|
+
*/
|
|
47
|
+
private static _wasShiftTabPressed;
|
|
40
48
|
/**
|
|
41
49
|
* Dispatches a custom event to notify each slotted child radio element
|
|
42
50
|
* when the radio group is disabled.
|
|
@@ -74,7 +82,65 @@ declare class PieRadioGroup_2 extends PieRadioGroup_base implements RadioGroupPr
|
|
|
74
82
|
*/
|
|
75
83
|
private _renderWrappedLabel;
|
|
76
84
|
protected updated(_changedProperties: PropertyValues<this>): void;
|
|
85
|
+
protected firstUpdated(): void;
|
|
77
86
|
connectedCallback(): void;
|
|
87
|
+
/**
|
|
88
|
+
* Updates the state of `_wasShiftTabPressed` based on the last `Tab` key press.
|
|
89
|
+
*/
|
|
90
|
+
private _updateShiftTabState;
|
|
91
|
+
/**
|
|
92
|
+
* Handles the `focusin` event to manage focus within the radio group.
|
|
93
|
+
*
|
|
94
|
+
* This method determines the appropriate element to focus when the radio group
|
|
95
|
+
* gains focus. It considers the last navigation action (whether `Shift+Tab` was used)
|
|
96
|
+
* and focuses the checked option, the first option, or the last option as needed.
|
|
97
|
+
*/
|
|
98
|
+
private _handleFocusIn;
|
|
99
|
+
/**
|
|
100
|
+
* Handles the `focusout` event to restore the `tabindex` on the radio group's `fieldset`.
|
|
101
|
+
*
|
|
102
|
+
* When focus leaves the radio group, this method enables the `tabindex` attribute
|
|
103
|
+
* on the `fieldset` element. This ensures the radio group remains accessible for
|
|
104
|
+
* keyboard navigation and can be re-focused when tabbing back into the group.
|
|
105
|
+
*/
|
|
106
|
+
private _handleFocusOut;
|
|
107
|
+
private _toggleFieldsetTabindex;
|
|
108
|
+
private _moveFocus;
|
|
109
|
+
/**
|
|
110
|
+
* Determines if a key press indicates forward navigation within the radio group.
|
|
111
|
+
*
|
|
112
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
113
|
+
* to forward navigation based on the current text direction (LTR or RTL).
|
|
114
|
+
*
|
|
115
|
+
* **Behaviour:**
|
|
116
|
+
* - For LTR (Left-to-Right) layouts:
|
|
117
|
+
* - `ArrowRight` and `ArrowDown` indicate forward navigation.
|
|
118
|
+
* - For RTL (Right-to-Left) layouts:
|
|
119
|
+
* - `ArrowLeft` and `ArrowDown` indicate forward navigation.
|
|
120
|
+
*/
|
|
121
|
+
private _isForwardKey;
|
|
122
|
+
/**
|
|
123
|
+
* Determines if a key press indicates backward navigation within the radio group.
|
|
124
|
+
*
|
|
125
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
126
|
+
* to backward navigation based on the current text direction (LTR or RTL).
|
|
127
|
+
*
|
|
128
|
+
* **Behaviour:**
|
|
129
|
+
* - For LTR (Left-to-Right) layouts:
|
|
130
|
+
* - `ArrowLeft` and `ArrowUp` indicate backward navigation.
|
|
131
|
+
* - For RTL (Right-to-Left) layouts:
|
|
132
|
+
* - `ArrowRight` and `ArrowUp` indicate backward navigation.
|
|
133
|
+
*/
|
|
134
|
+
private _isBackwardKey;
|
|
135
|
+
/**
|
|
136
|
+
* Handles keyboard navigation within the radio group using arrow keys.
|
|
137
|
+
*
|
|
138
|
+
* This method responds to `keydown` events and determines the appropriate navigation
|
|
139
|
+
* action (forward or backward) based on the pressed key and the current focus. It prevents
|
|
140
|
+
* the default browser behaviour (e.g., scrolling) when arrow keys are used for navigation.
|
|
141
|
+
*/
|
|
142
|
+
private _handleKeyDown;
|
|
143
|
+
private _focusAndClickOption;
|
|
78
144
|
disconnectedCallback(): void;
|
|
79
145
|
render(): TemplateResult<1>;
|
|
80
146
|
static styles: CSSResult;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justeattakeaway/pie-radio-group",
|
|
3
3
|
"description": "PIE Design System Radio Group built using Web Components",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"@custom-elements-manifest/analyzer": "0.9.0",
|
|
39
39
|
"@justeattakeaway/pie-components-config": "0.18.0",
|
|
40
40
|
"@justeattakeaway/pie-css": "0.13.1",
|
|
41
|
-
"@justeattakeaway/pie-radio": "0.
|
|
41
|
+
"@justeattakeaway/pie-radio": "0.6.0",
|
|
42
42
|
"cem-plugin-module-file-extensions": "0.0.5"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@justeattakeaway/pie-assistive-text": "0.8.
|
|
45
|
+
"@justeattakeaway/pie-assistive-text": "0.8.1",
|
|
46
46
|
"@justeattakeaway/pie-webc-core": "0.24.2"
|
|
47
47
|
},
|
|
48
48
|
"volta": {
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
type PropertyValues,
|
|
7
7
|
type TemplateResult,
|
|
8
8
|
} from 'lit';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
property, query, queryAssignedElements, state,
|
|
11
|
+
} from 'lit/decorators.js';
|
|
10
12
|
import {
|
|
11
13
|
RtlMixin,
|
|
12
14
|
defineCustomElement,
|
|
@@ -63,8 +65,19 @@ export class PieRadioGroup extends FormControlMixin(RtlMixin(LitElement)) implem
|
|
|
63
65
|
@queryAssignedElements({ selector: 'pie-radio' })
|
|
64
66
|
_slottedChildren!: Array<HTMLInputElement>;
|
|
65
67
|
|
|
68
|
+
@query('fieldset')
|
|
69
|
+
private _fieldset!: HTMLInputElement;
|
|
70
|
+
|
|
66
71
|
private _abortController!: AbortController;
|
|
67
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Tracks whether the `Shift` key was held during the last `Tab` key press.
|
|
75
|
+
*
|
|
76
|
+
* The property is static because it needs to be shared across all instances of the
|
|
77
|
+
* `PieRadioGroup` component on the same page, ensuring consistent behavior.
|
|
78
|
+
*/
|
|
79
|
+
private static _wasShiftTabPressed = false;
|
|
80
|
+
|
|
68
81
|
/**
|
|
69
82
|
* Dispatches a custom event to notify each slotted child radio element
|
|
70
83
|
* when the radio group is disabled.
|
|
@@ -147,12 +160,154 @@ export class PieRadioGroup extends FormControlMixin(RtlMixin(LitElement)) implem
|
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
|
|
163
|
+
protected firstUpdated (): void {
|
|
164
|
+
// Make all radios impossible to tab to
|
|
165
|
+
// This is because by default, we are able to tab to each individual radio button.
|
|
166
|
+
// This is not the behaviour we want, so applying -1 tabindex prevents it.
|
|
167
|
+
this._slottedChildren.forEach((radio) => radio.setAttribute('tabindex', '-1'));
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
connectedCallback (): void {
|
|
151
171
|
super.connectedCallback();
|
|
152
172
|
this._abortController = new AbortController();
|
|
153
173
|
const { signal } = this._abortController;
|
|
154
174
|
|
|
155
175
|
this.shadowRoot?.addEventListener('change', this._handleRadioChange.bind(this), { signal });
|
|
176
|
+
|
|
177
|
+
this.addEventListener('focusin', this._handleFocusIn, { signal });
|
|
178
|
+
this.addEventListener('focusout', this._handleFocusOut, { signal });
|
|
179
|
+
|
|
180
|
+
this.addEventListener('keydown', this._handleKeyDown, { signal });
|
|
181
|
+
document.addEventListener('keydown', this._updateShiftTabState.bind(this), { signal });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Updates the state of `_wasShiftTabPressed` based on the last `Tab` key press.
|
|
186
|
+
*/
|
|
187
|
+
private _updateShiftTabState (event: KeyboardEvent): void {
|
|
188
|
+
if (event.key === 'Tab') {
|
|
189
|
+
PieRadioGroup._wasShiftTabPressed = event.shiftKey;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Handles the `focusin` event to manage focus within the radio group.
|
|
195
|
+
*
|
|
196
|
+
* This method determines the appropriate element to focus when the radio group
|
|
197
|
+
* gains focus. It considers the last navigation action (whether `Shift+Tab` was used)
|
|
198
|
+
* and focuses the checked option, the first option, or the last option as needed.
|
|
199
|
+
*/
|
|
200
|
+
private _handleFocusIn (event: FocusEvent): void {
|
|
201
|
+
if (this !== event.target) return;
|
|
202
|
+
|
|
203
|
+
const isShiftTab = PieRadioGroup._wasShiftTabPressed;
|
|
204
|
+
const focusTarget = this._slottedChildren?.find((child) => child.checked) ||
|
|
205
|
+
(isShiftTab ? this._slottedChildren.at(-1) : this._slottedChildren[0]);
|
|
206
|
+
|
|
207
|
+
if (!focusTarget) return;
|
|
208
|
+
|
|
209
|
+
focusTarget.focus();
|
|
210
|
+
this._toggleFieldsetTabindex(false);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Handles the `focusout` event to restore the `tabindex` on the radio group's `fieldset`.
|
|
215
|
+
*
|
|
216
|
+
* When focus leaves the radio group, this method enables the `tabindex` attribute
|
|
217
|
+
* on the `fieldset` element. This ensures the radio group remains accessible for
|
|
218
|
+
* keyboard navigation and can be re-focused when tabbing back into the group.
|
|
219
|
+
*/
|
|
220
|
+
private _handleFocusOut (): void {
|
|
221
|
+
this._toggleFieldsetTabindex(true);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private _toggleFieldsetTabindex (enable: boolean): void {
|
|
225
|
+
if (enable) {
|
|
226
|
+
this._fieldset.setAttribute('tabindex', '0');
|
|
227
|
+
} else {
|
|
228
|
+
this._fieldset.removeAttribute('tabindex');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private _moveFocus (currentIndex: number, step: number): void {
|
|
233
|
+
const newIndex = (currentIndex + step + this._slottedChildren.length) % this._slottedChildren.length;
|
|
234
|
+
this._focusAndClickOption(this._slottedChildren[newIndex]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Determines if a key press indicates forward navigation within the radio group.
|
|
239
|
+
*
|
|
240
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
241
|
+
* to forward navigation based on the current text direction (LTR or RTL).
|
|
242
|
+
*
|
|
243
|
+
* **Behaviour:**
|
|
244
|
+
* - For LTR (Left-to-Right) layouts:
|
|
245
|
+
* - `ArrowRight` and `ArrowDown` indicate forward navigation.
|
|
246
|
+
* - For RTL (Right-to-Left) layouts:
|
|
247
|
+
* - `ArrowLeft` and `ArrowDown` indicate forward navigation.
|
|
248
|
+
*/
|
|
249
|
+
private _isForwardKey (event: KeyboardEvent): boolean {
|
|
250
|
+
return (event.code === 'ArrowRight' && !this.isRTL) ||
|
|
251
|
+
(event.code === 'ArrowLeft' && this.isRTL) ||
|
|
252
|
+
event.code === 'ArrowDown';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Determines if a key press indicates backward navigation within the radio group.
|
|
257
|
+
*
|
|
258
|
+
* This method evaluates a keyboard event to check if the pressed key corresponds
|
|
259
|
+
* to backward navigation based on the current text direction (LTR or RTL).
|
|
260
|
+
*
|
|
261
|
+
* **Behaviour:**
|
|
262
|
+
* - For LTR (Left-to-Right) layouts:
|
|
263
|
+
* - `ArrowLeft` and `ArrowUp` indicate backward navigation.
|
|
264
|
+
* - For RTL (Right-to-Left) layouts:
|
|
265
|
+
* - `ArrowRight` and `ArrowUp` indicate backward navigation.
|
|
266
|
+
*/
|
|
267
|
+
private _isBackwardKey (event: KeyboardEvent): boolean {
|
|
268
|
+
return (event.code === 'ArrowLeft' && !this.isRTL) ||
|
|
269
|
+
(event.code === 'ArrowRight' && this.isRTL) ||
|
|
270
|
+
event.code === 'ArrowUp';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Handles keyboard navigation within the radio group using arrow keys.
|
|
275
|
+
*
|
|
276
|
+
* This method responds to `keydown` events and determines the appropriate navigation
|
|
277
|
+
* action (forward or backward) based on the pressed key and the current focus. It prevents
|
|
278
|
+
* the default browser behaviour (e.g., scrolling) when arrow keys are used for navigation.
|
|
279
|
+
*/
|
|
280
|
+
private _handleKeyDown (event: KeyboardEvent): void {
|
|
281
|
+
const currentlyFocusedChild = this._slottedChildren.find((child) => child === document.activeElement);
|
|
282
|
+
|
|
283
|
+
if (!currentlyFocusedChild) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const currentIndex = this._slottedChildren.indexOf(currentlyFocusedChild);
|
|
288
|
+
if (currentIndex === -1) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Prevent default scrolling behavior when using Arrow keys for Radio Group navigation
|
|
293
|
+
if (['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'].includes(event.code)) {
|
|
294
|
+
event.preventDefault();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this._isForwardKey(event)) {
|
|
298
|
+
this._moveFocus(currentIndex, 1);
|
|
299
|
+
} else if (this._isBackwardKey(event)) {
|
|
300
|
+
this._moveFocus(currentIndex, -1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private _focusAndClickOption (option: HTMLInputElement): void {
|
|
305
|
+
option.focus();
|
|
306
|
+
// This is quite hacky, but it ensures the radio elements correct emit a real change event.
|
|
307
|
+
// Simply setting option.checked as true would require re-architecture of both this component and the radio button
|
|
308
|
+
// to ensure that property changes are observed and correctly propagated up.
|
|
309
|
+
option.shadowRoot?.querySelector('input')?.click();
|
|
310
|
+
this._toggleFieldsetTabindex(false);
|
|
156
311
|
}
|
|
157
312
|
|
|
158
313
|
disconnectedCallback (): void {
|
|
@@ -179,6 +334,7 @@ export class PieRadioGroup extends FormControlMixin(RtlMixin(LitElement)) implem
|
|
|
179
334
|
|
|
180
335
|
return html`
|
|
181
336
|
<fieldset
|
|
337
|
+
tabindex="0"
|
|
182
338
|
name=${ifDefined(name)}
|
|
183
339
|
?disabled=${disabled}
|
|
184
340
|
data-test-id="pie-radio-group"
|