@fluentui/web-components 3.0.0-beta.39 → 3.0.0-beta.40

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +13 -2
  2. package/dist/dts/accordion/accordion.d.ts +35 -12
  3. package/dist/dts/accordion-item/accordion-item.d.ts +42 -14
  4. package/dist/dts/accordion-item/accordion-item.options.d.ts +2 -2
  5. package/dist/dts/accordion-item/index.d.ts +1 -1
  6. package/dist/dts/checkbox/checkbox.d.ts +98 -48
  7. package/dist/dts/field/field.d.ts +30 -1
  8. package/dist/dts/field/field.options.d.ts +2 -0
  9. package/dist/dts/index.d.ts +1 -1
  10. package/dist/dts/radio/index.d.ts +1 -1
  11. package/dist/dts/radio/radio.d.ts +38 -35
  12. package/dist/dts/radio/radio.options.d.ts +14 -0
  13. package/dist/dts/radio/radio.styles.d.ts +3 -1
  14. package/dist/dts/radio/radio.template.d.ts +13 -1
  15. package/dist/dts/radio-group/radio-group.d.ts +211 -49
  16. package/dist/dts/radio-group/radio-group.template.d.ts +1 -1
  17. package/dist/dts/styles/states/index.d.ts +20 -0
  18. package/dist/dts/switch/switch.d.ts +1 -0
  19. package/dist/dts/utils/root-active-element.d.ts +1 -0
  20. package/dist/esm/accordion/accordion.js +46 -85
  21. package/dist/esm/accordion/accordion.js.map +1 -1
  22. package/dist/esm/accordion-item/accordion-item.js +63 -19
  23. package/dist/esm/accordion-item/accordion-item.js.map +1 -1
  24. package/dist/esm/accordion-item/accordion-item.options.js +1 -1
  25. package/dist/esm/accordion-item/accordion-item.options.js.map +1 -1
  26. package/dist/esm/accordion-item/accordion-item.styles.js +41 -63
  27. package/dist/esm/accordion-item/accordion-item.styles.js.map +1 -1
  28. package/dist/esm/accordion-item/accordion-item.template.js +24 -43
  29. package/dist/esm/accordion-item/accordion-item.template.js.map +1 -1
  30. package/dist/esm/accordion-item/index.js +1 -1
  31. package/dist/esm/accordion-item/index.js.map +1 -1
  32. package/dist/esm/checkbox/checkbox.js +146 -97
  33. package/dist/esm/checkbox/checkbox.js.map +1 -1
  34. package/dist/esm/checkbox/checkbox.styles.js +1 -6
  35. package/dist/esm/checkbox/checkbox.styles.js.map +1 -1
  36. package/dist/esm/checkbox/checkbox.template.js.map +1 -1
  37. package/dist/esm/field/field.js +91 -29
  38. package/dist/esm/field/field.js.map +1 -1
  39. package/dist/esm/field/field.options.js.map +1 -1
  40. package/dist/esm/field/field.styles.js +31 -16
  41. package/dist/esm/field/field.styles.js.map +1 -1
  42. package/dist/esm/field/field.template.js +1 -1
  43. package/dist/esm/field/field.template.js.map +1 -1
  44. package/dist/esm/index.js +1 -1
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/radio/radio.js +59 -72
  47. package/dist/esm/radio/radio.js.map +1 -1
  48. package/dist/esm/radio/radio.options.js +2 -0
  49. package/dist/esm/radio/radio.options.js.map +1 -0
  50. package/dist/esm/radio/radio.styles.js +95 -88
  51. package/dist/esm/radio/radio.styles.js.map +1 -1
  52. package/dist/esm/radio/radio.template.js +21 -24
  53. package/dist/esm/radio/radio.template.js.map +1 -1
  54. package/dist/esm/radio-group/radio-group.js +416 -313
  55. package/dist/esm/radio-group/radio-group.js.map +1 -1
  56. package/dist/esm/radio-group/radio-group.styles.js +26 -32
  57. package/dist/esm/radio-group/radio-group.styles.js.map +1 -1
  58. package/dist/esm/radio-group/radio-group.template.js +6 -21
  59. package/dist/esm/radio-group/radio-group.template.js.map +1 -1
  60. package/dist/esm/styles/states/index.js +20 -0
  61. package/dist/esm/styles/states/index.js.map +1 -1
  62. package/dist/esm/switch/switch.js +4 -0
  63. package/dist/esm/switch/switch.js.map +1 -1
  64. package/dist/esm/switch/switch.styles.js +3 -6
  65. package/dist/esm/switch/switch.styles.js.map +1 -1
  66. package/dist/esm/switch/switch.template.js.map +1 -1
  67. package/dist/esm/theme/set-theme.js +3 -6
  68. package/dist/esm/theme/set-theme.js.map +1 -1
  69. package/dist/esm/utils/root-active-element.js +9 -0
  70. package/dist/esm/utils/root-active-element.js.map +1 -0
  71. package/dist/web-components.d.ts +461 -181
  72. package/dist/web-components.js +1387 -1233
  73. package/dist/web-components.min.js +262 -258
  74. package/package.json +1 -1
  75. package/dist/dts/radio/radio.form-associated.d.ts +0 -14
  76. package/dist/esm/radio/radio.form-associated.js +0 -14
  77. package/dist/esm/radio/radio.form-associated.js.map +0 -1
@@ -1,367 +1,470 @@
1
1
  import { __decorate } from "tslib";
2
- import { attr, FASTElement, observable } from '@microsoft/fast-element';
3
- import { ArrowKeys, Direction, keyArrowDown, keyArrowLeft, keyArrowRight, keyArrowUp, keyEnter, } from '@microsoft/fast-web-utilities';
2
+ import { attr, FASTElement, Observable, observable, Updates } from '@microsoft/fast-element';
3
+ import { findLastIndex } from '@microsoft/fast-web-utilities';
4
4
  import { Radio } from '../radio/radio.js';
5
- import { getDirection } from '../utils/index.js';
5
+ import { getDirection } from '../utils/direction.js';
6
+ import { getRootActiveElement } from '../utils/root-active-element.js';
6
7
  import { RadioGroupOrientation } from './radio-group.options.js';
7
8
  /**
8
- * The base class used for constructing a fluent-radio-group custom element
9
+ * A Radio Group Custom HTML Element.
10
+ * Implements the {@link https://w3c.github.io/aria/#radiogroup | ARIA `radiogroup` role}.
11
+ *
9
12
  * @public
13
+ *
14
+ * @slot - The default slot for the radio group
10
15
  */
11
16
  export class RadioGroup extends FASTElement {
17
+ /**
18
+ * Sets the checked state of the nearest enabled radio when the `checkedIndex` changes.
19
+ *
20
+ * @param prev - the previous index
21
+ * @param next - the current index
22
+ * @internal
23
+ */
24
+ checkedIndexChanged(prev, next) {
25
+ if (!this.enabledRadios) {
26
+ return;
27
+ }
28
+ this.checkRadio(next);
29
+ }
30
+ /**
31
+ * Sets the `disabled` attribute on all child radios when the `disabled` property changes.
32
+ *
33
+ * @param prev - the previous disabled value
34
+ * @param next - the current disabled value
35
+ * @internal
36
+ */
37
+ disabledChanged(prev, next) {
38
+ var _a;
39
+ if (this.$fastController.isConnected) {
40
+ this.checkedIndex = -1;
41
+ (_a = this.radios) === null || _a === void 0 ? void 0 : _a.forEach(radio => {
42
+ radio.disabled = radio.disabledAttribute || this.disabled;
43
+ });
44
+ this.restrictFocus();
45
+ }
46
+ }
47
+ /**
48
+ * Sets the matching radio to checked when the value changes. If no radio matches the value, no radio will be checked.
49
+ *
50
+ * @param prev - the previous value
51
+ * @param next - the current value
52
+ */
53
+ initialValueChanged(prev, next) {
54
+ this.value = next !== null && next !== void 0 ? next : '';
55
+ }
56
+ /**
57
+ * Sets the `name` attribute on all child radios when the `name` property changes.
58
+ *
59
+ * @internal
60
+ */
61
+ nameChanged(prev, next) {
62
+ var _a;
63
+ if (this.isConnected && next) {
64
+ (_a = this.radios) === null || _a === void 0 ? void 0 : _a.forEach(radio => {
65
+ radio.name = this.name;
66
+ });
67
+ }
68
+ }
69
+ /**
70
+ * Sets the ariaOrientation attribute when the orientation changes.
71
+ *
72
+ * @param prev - the previous orientation
73
+ * @param next - the current orientation
74
+ * @internal
75
+ */
76
+ orientationChanged(prev, next) {
77
+ var _a;
78
+ this.elementInternals.ariaOrientation = (_a = this.orientation) !== null && _a !== void 0 ? _a : RadioGroupOrientation.horizontal;
79
+ }
80
+ /**
81
+ * Updates the enabled radios collection when properties on the child radios change.
82
+ *
83
+ * @param prev - the previous radios
84
+ * @param next - the current radios
85
+ */
86
+ radiosChanged(prev, next) {
87
+ const setSize = next === null || next === void 0 ? void 0 : next.length;
88
+ if (!setSize) {
89
+ return;
90
+ }
91
+ if (!this.name && next.every(x => x.name === next[0].name)) {
92
+ this.name = next[0].name;
93
+ }
94
+ const checkedIndex = findLastIndex(this.enabledRadios, x => x.initialChecked);
95
+ next.forEach((radio, index) => {
96
+ var _a;
97
+ radio.ariaPosInSet = `${index + 1}`;
98
+ radio.ariaSetSize = `${setSize}`;
99
+ if (this.initialValue && !this.dirtyState) {
100
+ radio.checked = radio.value === this.initialValue;
101
+ }
102
+ else {
103
+ radio.checked = index === checkedIndex;
104
+ }
105
+ radio.name = (_a = this.name) !== null && _a !== void 0 ? _a : radio.name;
106
+ radio.disabled = this.disabled || radio.disabledAttribute;
107
+ });
108
+ if (!this.dirtyState && this.initialValue) {
109
+ this.value = this.initialValue;
110
+ }
111
+ if (!this.value) {
112
+ // TODO: Switch to standard `Array.findLastIndex` when TypeScript 5 is available
113
+ this.checkedIndex = checkedIndex;
114
+ }
115
+ // prettier-ignore
116
+ const radioIds = next.map(radio => radio.id).join(' ').trim();
117
+ if (radioIds) {
118
+ this.setAttribute('aria-owns', radioIds);
119
+ }
120
+ Updates.enqueue(() => {
121
+ this.restrictFocus();
122
+ });
123
+ }
124
+ /**
125
+ *
126
+ * @param prev - the previous required value
127
+ * @param next - the current required value
128
+ */
129
+ requiredChanged(prev, next) {
130
+ this.elementInternals.ariaRequired = next ? 'true' : null;
131
+ this.setValidity();
132
+ }
133
+ /**
134
+ * A collection of child radios that are not disabled.
135
+ *
136
+ * @internal
137
+ */
138
+ get enabledRadios() {
139
+ var _a, _b;
140
+ if (this.disabled) {
141
+ return [];
142
+ }
143
+ return (_b = (_a = this.radios) === null || _a === void 0 ? void 0 : _a.filter(x => !x.disabled)) !== null && _b !== void 0 ? _b : [];
144
+ }
145
+ /**
146
+ * The validation message. Uses the browser's default validation message for native checkboxes if not otherwise
147
+ * specified (e.g., via `setCustomValidity`).
148
+ *
149
+ * @internal
150
+ */
151
+ get validationMessage() {
152
+ var _a, _b;
153
+ if (this.elementInternals.validationMessage) {
154
+ return this.elementInternals.validationMessage;
155
+ }
156
+ if ((_b = (_a = this.enabledRadios) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.validationMessage) {
157
+ return this.enabledRadios[0].validationMessage;
158
+ }
159
+ if (!this._validationFallbackMessage) {
160
+ const validationMessageFallbackControl = document.createElement('input');
161
+ validationMessageFallbackControl.type = 'radio';
162
+ validationMessageFallbackControl.required = true;
163
+ validationMessageFallbackControl.checked = false;
164
+ this._validationFallbackMessage = validationMessageFallbackControl.validationMessage;
165
+ }
166
+ return this._validationFallbackMessage;
167
+ }
168
+ /**
169
+ * The element's validity state.
170
+ *
171
+ * @public
172
+ * @remarks
173
+ * Reflects the {@link https://developer.mozilla.org/docs/Web/API/ElementInternals/validity | `ElementInternals.validity`} property.
174
+ */
175
+ get validity() {
176
+ return this.elementInternals.validity;
177
+ }
178
+ /**
179
+ * The current value of the checked radio.
180
+ *
181
+ * @public
182
+ */
183
+ get value() {
184
+ var _a, _b;
185
+ Observable.notify(this, 'value');
186
+ return (_b = (_a = this.enabledRadios.find(x => x.checked)) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null;
187
+ }
188
+ set value(next) {
189
+ const index = this.enabledRadios.findIndex(x => x.value === next);
190
+ this.checkedIndex = index;
191
+ if (this.$fastController.isConnected) {
192
+ this.setFormValue(next);
193
+ this.setValidity();
194
+ }
195
+ Observable.track(this, 'value');
196
+ }
197
+ /**
198
+ * Sets the checked state of all radios when any radio emits a `change` event.
199
+ *
200
+ * @param e - the change event
201
+ */
202
+ changeHandler(e) {
203
+ if (this === e.target) {
204
+ return true;
205
+ }
206
+ this.dirtyState = true;
207
+ const radioIndex = this.enabledRadios.indexOf(e.target);
208
+ this.checkRadio(radioIndex);
209
+ return true;
210
+ }
211
+ /**
212
+ * Checks the radio at the specified index.
213
+ *
214
+ * @param index - the index of the radio to check
215
+ * @internal
216
+ */
217
+ checkRadio(index = this.checkedIndex) {
218
+ let checkedIndex = this.checkedIndex;
219
+ this.enabledRadios.forEach((item, i) => {
220
+ const shouldCheck = i === index;
221
+ item.checked = shouldCheck;
222
+ if (shouldCheck) {
223
+ checkedIndex = i;
224
+ }
225
+ });
226
+ this.checkedIndex = checkedIndex;
227
+ this.setFormValue(this.value);
228
+ this.setValidity();
229
+ }
230
+ /**
231
+ * Checks the validity of the element and returns the result.
232
+ *
233
+ * @public
234
+ * @remarks
235
+ * Reflects the {@link https://developer.mozilla.org/docs/Web/API/ElementInternals/checkValidity | `HTMLInputElement.checkValidity()`} method.
236
+ */
237
+ checkValidity() {
238
+ return this.elementInternals.checkValidity();
239
+ }
240
+ /**
241
+ * Handles click events for the radio group.
242
+ *
243
+ * @param e - the click event
244
+ * @internal
245
+ */
246
+ clickHandler(e) {
247
+ var _a;
248
+ if (this === e.target) {
249
+ (_a = this.enabledRadios[Math.max(0, this.checkedIndex)]) === null || _a === void 0 ? void 0 : _a.focus();
250
+ }
251
+ return true;
252
+ }
12
253
  constructor() {
13
- super(...arguments);
254
+ var _a;
255
+ super();
14
256
  /**
15
- * sets radio layout styles
16
- *
17
- * @public
18
- * @remarks
19
- * HTML Attribute: stacked
257
+ * Indicates that the value has been changed by the user.
20
258
  */
21
- this.stacked = false;
259
+ this.dirtyState = false;
22
260
  /**
23
- * The orientation of the group
261
+ * Disables the radio group and child radios.
24
262
  *
25
263
  * @public
26
264
  * @remarks
27
- * HTML Attribute: orientation
265
+ * HTML Attribute: `disabled`
28
266
  */
29
- this.orientation = RadioGroupOrientation.horizontal;
30
- this.radioChangeHandler = (e) => {
31
- const changedRadio = e.target;
32
- if (changedRadio.checked) {
33
- this.slottedRadioButtons.forEach((radio) => {
34
- if (radio instanceof Radio && radio !== changedRadio) {
35
- radio.checked = false;
36
- if (!this.isInsideFoundationToolbar) {
37
- radio.setAttribute('tabindex', '-1');
38
- }
39
- }
40
- });
41
- this.selectedRadio = changedRadio;
42
- this.value = changedRadio.value;
43
- changedRadio.setAttribute('tabindex', '0');
44
- this.focusedRadio = changedRadio;
45
- }
46
- e.stopPropagation();
47
- };
48
- this.moveToRadioByIndex = (group, index) => {
49
- const radio = group[index];
50
- if (!this.isInsideToolbar) {
51
- radio.setAttribute('tabindex', '0');
52
- radio.checked = true;
53
- this.selectedRadio = radio;
54
- }
55
- this.focusedRadio = radio;
56
- radio.focus();
57
- };
58
- this.moveRightOffGroup = () => {
59
- var _a;
60
- (_a = this.nextElementSibling) === null || _a === void 0 ? void 0 : _a.focus();
61
- };
62
- this.moveLeftOffGroup = () => {
63
- var _a;
64
- (_a = this.previousElementSibling) === null || _a === void 0 ? void 0 : _a.focus();
65
- };
66
- /**
67
- * @internal
68
- */
69
- this.focusOutHandler = (e) => {
70
- const group = this.slottedRadioButtons;
71
- const radio = e.target;
72
- const index = radio !== null ? group.indexOf(radio) : 0;
73
- const focusedIndex = this.focusedRadio ? group.indexOf(this.focusedRadio) : -1;
74
- if ((focusedIndex === 0 && index === focusedIndex) ||
75
- (focusedIndex === group.length - 1 && focusedIndex === index)) {
76
- if (!this.selectedRadio) {
77
- this.focusedRadio = group[0];
78
- this.focusedRadio.setAttribute('tabindex', '0');
79
- group.forEach((nextRadio) => {
80
- if (radio instanceof Radio && nextRadio !== this.focusedRadio) {
81
- nextRadio.setAttribute('tabindex', '-1');
82
- }
83
- });
84
- }
85
- else {
86
- this.focusedRadio = this.selectedRadio;
87
- if (!this.isInsideFoundationToolbar) {
88
- this.selectedRadio.setAttribute('tabindex', '0');
89
- group.forEach((nextRadio) => {
90
- if (nextRadio !== this.selectedRadio) {
91
- nextRadio.setAttribute('tabindex', '-1');
92
- }
93
- });
94
- }
95
- }
96
- }
97
- return true;
98
- };
99
- /**
100
- * @internal
101
- */
102
- this.handleDisabledClick = (e) => {
103
- // prevent focus events on items from the click handler when disabled
104
- if (this.disabled) {
105
- e.preventDefault();
106
- return;
107
- }
108
- return true;
109
- };
267
+ this.disabled = false;
110
268
  /**
269
+ * The internal {@link https://developer.mozilla.org/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component.
270
+ *
111
271
  * @internal
112
272
  */
113
- this.clickHandler = (e) => {
114
- if (this.disabled) {
115
- return;
116
- }
117
- e.preventDefault();
118
- const radio = e.target;
119
- if (radio && radio instanceof Radio) {
120
- radio.checked = true;
121
- radio.setAttribute('tabindex', '0');
122
- this.selectedRadio = radio;
123
- this.focusedRadio = radio;
124
- }
125
- };
126
- this.shouldMoveOffGroupToTheRight = (index, group, key) => {
127
- return index === group.length && this.isInsideToolbar && key === keyArrowRight;
128
- };
129
- this.shouldMoveOffGroupToTheLeft = (group, key) => {
130
- const index = this.focusedRadio ? group.indexOf(this.focusedRadio) - 1 : 0;
131
- return index < 0 && this.isInsideToolbar && key === keyArrowLeft;
132
- };
133
- this.checkFocusedRadio = () => {
134
- if (this.focusedRadio !== null && !this.focusedRadio.checked) {
135
- this.focusedRadio.checked = true;
136
- this.focusedRadio.setAttribute('tabindex', '0');
137
- this.focusedRadio.focus();
138
- this.selectedRadio = this.focusedRadio;
139
- }
140
- };
141
- this.moveRight = (e) => {
142
- const group = this.slottedRadioButtons;
143
- let index = 0;
144
- index = this.focusedRadio ? group.indexOf(this.focusedRadio) + 1 : 1;
145
- if (this.shouldMoveOffGroupToTheRight(index, group, e.key)) {
146
- this.moveRightOffGroup();
147
- return;
148
- }
149
- else if (index === group.length) {
150
- index = 0;
273
+ this.elementInternals = this.attachInternals();
274
+ this.elementInternals.role = 'radiogroup';
275
+ this.elementInternals.ariaOrientation = (_a = this.orientation) !== null && _a !== void 0 ? _a : RadioGroupOrientation.horizontal;
276
+ }
277
+ /**
278
+ * Focuses the checked radio or the first enabled radio.
279
+ *
280
+ * @internal
281
+ */
282
+ focus() {
283
+ var _a;
284
+ (_a = this.enabledRadios[Math.max(0, this.checkedIndex)]) === null || _a === void 0 ? void 0 : _a.focus();
285
+ }
286
+ /**
287
+ * Enables tabbing through the radio group when the group receives focus.
288
+ *
289
+ * @param e - the focus event
290
+ * @internal
291
+ */
292
+ focusinHandler(e) {
293
+ if (!this.disabled) {
294
+ this.enabledRadios.forEach(radio => {
295
+ radio.tabIndex = 0;
296
+ });
297
+ }
298
+ return true;
299
+ }
300
+ /**
301
+ * Sets the tabindex of the radios based on the checked state when the radio group loses focus.
302
+ *
303
+ * @param e - the focusout event
304
+ * @internal
305
+ */
306
+ focusoutHandler(e) {
307
+ var _a, _b;
308
+ if (((_a = this.radios) === null || _a === void 0 ? void 0 : _a.includes(e.relatedTarget)) && ((_b = this.radios) === null || _b === void 0 ? void 0 : _b.some(x => x.checked))) {
309
+ this.restrictFocus();
310
+ }
311
+ return true;
312
+ }
313
+ formResetCallback() {
314
+ this.dirtyState = false;
315
+ this.checkedIndex = -1;
316
+ this.setFormValue(this.value);
317
+ this.setValidity();
318
+ }
319
+ getEnabledIndexInBounds(index, upperBound = this.enabledRadios.length) {
320
+ if (upperBound === 0) {
321
+ return -1;
322
+ }
323
+ return (index + upperBound) % upperBound;
324
+ }
325
+ /**
326
+ * Handles keydown events for the radio group.
327
+ *
328
+ * @param e - the keyboard event
329
+ * @internal
330
+ */
331
+ keydownHandler(e) {
332
+ var _a, _b;
333
+ const isRtl = getDirection(this) === 'rtl';
334
+ const checkedIndex = (_a = this.enabledRadios.findIndex(x => x === getRootActiveElement(this))) !== null && _a !== void 0 ? _a : this.checkedIndex;
335
+ let increment = 0;
336
+ switch (e.key) {
337
+ case 'ArrowLeft': {
338
+ increment = isRtl ? 1 : -1;
339
+ break;
151
340
  }
152
- /* looping to get to next radio that is not disabled */
153
- /* matching native radio/radiogroup which does not select an item if there is only 1 in the group */
154
- while (index < group.length && group.length > 1) {
155
- if (!group[index].disabled) {
156
- this.moveToRadioByIndex(group, index);
157
- break;
158
- }
159
- else if (this.focusedRadio && index === group.indexOf(this.focusedRadio)) {
160
- break;
161
- }
162
- else if (index + 1 >= group.length) {
163
- if (this.isInsideToolbar) {
164
- break;
165
- }
166
- else {
167
- index = 0;
168
- }
169
- }
170
- else {
171
- index += 1;
172
- }
341
+ case 'ArrowUp': {
342
+ increment = -1;
343
+ break;
173
344
  }
174
- };
175
- this.moveLeft = (e) => {
176
- const group = this.slottedRadioButtons;
177
- let index = 0;
178
- index = this.focusedRadio ? group.indexOf(this.focusedRadio) - 1 : 0;
179
- index = index < 0 ? group.length - 1 : index;
180
- if (this.shouldMoveOffGroupToTheLeft(group, e.key)) {
181
- this.moveLeftOffGroup();
182
- return;
345
+ case 'ArrowRight': {
346
+ increment = isRtl ? -1 : 1;
347
+ break;
183
348
  }
184
- /* looping to get to next radio that is not disabled */
185
- while (index >= 0 && group.length > 1) {
186
- if (!group[index].disabled) {
187
- this.moveToRadioByIndex(group, index);
188
- break;
189
- }
190
- else if (this.focusedRadio && index === group.indexOf(this.focusedRadio)) {
191
- break;
192
- }
193
- else if (index - 1 < 0) {
194
- index = group.length - 1;
195
- }
196
- else {
197
- index -= 1;
198
- }
349
+ case 'ArrowDown': {
350
+ increment = 1;
351
+ break;
199
352
  }
200
- };
201
- /**
202
- * keyboard handling per https://w3c.github.io/aria-practices/#for-radio-groups-not-contained-in-a-toolbar
203
- * navigation is different when there is an ancestor with role='toolbar'
204
- *
205
- * @internal
206
- */
207
- this.keydownHandler = (e) => {
208
- const key = e.key;
209
- if (key in ArrowKeys && (this.isInsideFoundationToolbar || this.disabled)) {
210
- return true;
353
+ case 'Tab': {
354
+ this.restrictFocus();
355
+ break;
211
356
  }
212
- switch (key) {
213
- case keyEnter: {
214
- this.checkFocusedRadio();
215
- break;
216
- }
217
- case keyArrowRight:
218
- case keyArrowDown: {
219
- if (this.direction === Direction.ltr) {
220
- this.moveRight(e);
221
- }
222
- else {
223
- this.moveLeft(e);
224
- }
225
- break;
226
- }
227
- case keyArrowLeft:
228
- case keyArrowUp: {
229
- if (this.direction === Direction.ltr) {
230
- this.moveLeft(e);
231
- }
232
- else {
233
- this.moveRight(e);
234
- }
235
- break;
236
- }
237
- default: {
238
- return true;
239
- }
357
+ case ' ': {
358
+ this.checkRadio();
359
+ break;
240
360
  }
241
- };
242
- }
243
- nameChanged() {
244
- if (this.slottedRadioButtons) {
245
- this.slottedRadioButtons.forEach((radio) => {
246
- radio.setAttribute('name', this.name);
247
- });
248
361
  }
249
- }
250
- valueChanged() {
251
- if (this.slottedRadioButtons) {
252
- this.slottedRadioButtons.forEach((radio) => {
253
- if (radio instanceof Radio) {
254
- if (radio.value === this.value) {
255
- radio.checked = true;
256
- this.selectedRadio = radio;
257
- }
258
- }
259
- });
362
+ if (!increment) {
363
+ return true;
260
364
  }
261
- this.$emit('change');
365
+ const nextIndex = checkedIndex + increment;
366
+ this.checkedIndex = this.getEnabledIndexInBounds(nextIndex);
367
+ (_b = this.enabledRadios[this.checkedIndex]) === null || _b === void 0 ? void 0 : _b.focus();
262
368
  }
263
- slottedRadioButtonsChanged(oldValue, newValue) {
264
- if (this.slottedRadioButtons && this.slottedRadioButtons.length > 0) {
265
- this.setupRadioButtons();
369
+ /**
370
+ *
371
+ * @param e - the disabled event
372
+ */
373
+ disabledRadioHandler(e) {
374
+ if (e.detail === true && e.target.checked) {
375
+ this.checkedIndex = -1;
266
376
  }
267
377
  }
268
- get parentToolbar() {
269
- return this.closest('[role="toolbar"]');
270
- }
271
- get isInsideToolbar() {
272
- var _a;
273
- return ((_a = this.parentToolbar) !== null && _a !== void 0 ? _a : false);
378
+ /**
379
+ * Reports the validity of the element.
380
+ *
381
+ * @public
382
+ * @remarks
383
+ * Reflects the {@link https://developer.mozilla.org/docs/Web/API/ElementInternals/reportValidity | `HTMLInputElement.reportValidity()`} method.
384
+ */
385
+ reportValidity() {
386
+ return this.elementInternals.reportValidity();
274
387
  }
275
- get isInsideFoundationToolbar() {
276
- var _a;
277
- return !!((_a = this.parentToolbar) === null || _a === void 0 ? void 0 : _a.hasOwnProperty('$fastController'));
388
+ /**
389
+ * Resets the `tabIndex` for all child radios when the radio group loses focus.
390
+ *
391
+ * @internal
392
+ */
393
+ restrictFocus() {
394
+ let activeIndex = Math.max(this.checkedIndex, 0);
395
+ const focusedRadioIndex = this.enabledRadios.indexOf(getRootActiveElement(this));
396
+ if (focusedRadioIndex !== -1) {
397
+ activeIndex = focusedRadioIndex;
398
+ }
399
+ activeIndex = this.getEnabledIndexInBounds(activeIndex);
400
+ this.enabledRadios.forEach((item, index) => {
401
+ item.tabIndex = index === activeIndex ? 0 : -1;
402
+ });
278
403
  }
279
404
  /**
405
+ * Reflects the {@link https://developer.mozilla.org/docs/Web/API/ElementInternals/setFormValue | `ElementInternals.setFormValue()`} method.
406
+ *
280
407
  * @internal
281
408
  */
282
- connectedCallback() {
283
- super.connectedCallback();
284
- this.direction = getDirection(this);
285
- this.setupRadioButtons();
409
+ setFormValue(value, state) {
410
+ this.elementInternals.setFormValue(value, value !== null && value !== void 0 ? value : state);
286
411
  }
287
- disconnectedCallback() {
288
- this.slottedRadioButtons.forEach((radio) => {
289
- if (radio instanceof Radio) {
290
- radio.removeEventListener('change', this.radioChangeHandler);
412
+ /**
413
+ * Sets the validity of the element.
414
+ *
415
+ * @param flags - Validity flags to set.
416
+ * @param message - Optional message to supply. If not provided, the element's `validationMessage` will be used.
417
+ * @param anchor - Optional anchor to use for the validation message.
418
+ *
419
+ * @internal
420
+ */
421
+ setValidity(flags, message, anchor) {
422
+ if (this.$fastController.isConnected) {
423
+ if (this.disabled || !this.required) {
424
+ this.elementInternals.setValidity({});
425
+ return;
291
426
  }
292
- });
293
- }
294
- setupRadioButtons() {
295
- const checkedRadios = this.slottedRadioButtons.filter((radio) => {
296
- return radio.hasAttribute('checked');
297
- });
298
- const numberOfCheckedRadios = checkedRadios ? checkedRadios.length : 0;
299
- if (numberOfCheckedRadios > 1) {
300
- const lastCheckedRadio = checkedRadios[numberOfCheckedRadios - 1];
301
- lastCheckedRadio.checked = true;
427
+ this.elementInternals.setValidity({ valueMissing: this.required && !this.value, ...flags }, message !== null && message !== void 0 ? message : this.validationMessage, anchor !== null && anchor !== void 0 ? anchor : this.enabledRadios[0]);
302
428
  }
303
- let foundMatchingVal = false;
304
- this.slottedRadioButtons.forEach((radio) => {
305
- if (radio instanceof Radio) {
306
- if (this.name !== undefined) {
307
- radio.setAttribute('name', this.name);
308
- }
309
- if (this.value && this.value === radio.value) {
310
- this.selectedRadio = radio;
311
- this.focusedRadio = radio;
312
- radio.checked = true;
313
- radio.setAttribute('tabindex', '0');
314
- foundMatchingVal = true;
315
- }
316
- else {
317
- if (!this.isInsideFoundationToolbar) {
318
- radio.setAttribute('tabindex', '-1');
319
- }
320
- radio.checked = false;
321
- }
322
- radio.addEventListener('change', this.radioChangeHandler);
323
- }
429
+ }
430
+ /**
431
+ * Updates the collection of child radios when the slot changes.
432
+ *
433
+ * @param e - the slot change event
434
+ * @internal
435
+ */
436
+ slotchangeHandler(e) {
437
+ Updates.enqueue(() => {
438
+ this.radios = [...this.querySelectorAll('*')].filter(x => x instanceof Radio);
324
439
  });
325
- if (this.value === undefined && this.slottedRadioButtons.length > 0) {
326
- const checkedRadios = this.slottedRadioButtons.filter((radio) => {
327
- return radio.hasAttribute('checked');
328
- });
329
- const numberOfCheckedRadios = checkedRadios !== null ? checkedRadios.length : 0;
330
- if (numberOfCheckedRadios > 0 && !foundMatchingVal) {
331
- const lastCheckedRadio = checkedRadios[numberOfCheckedRadios - 1];
332
- lastCheckedRadio.checked = true;
333
- this.focusedRadio = lastCheckedRadio;
334
- lastCheckedRadio.setAttribute('tabindex', '0');
335
- }
336
- else {
337
- this.slottedRadioButtons[0].setAttribute('tabindex', '0');
338
- this.focusedRadio = this.slottedRadioButtons[0];
339
- }
340
- }
341
440
  }
342
441
  }
442
+ /**
443
+ * The form-associated flag.
444
+ * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example | Form-associated custom elements}
445
+ *
446
+ * @public
447
+ */
448
+ RadioGroup.formAssociated = true;
343
449
  __decorate([
344
- attr({ mode: 'boolean' })
345
- ], RadioGroup.prototype, "stacked", void 0);
346
- __decorate([
347
- attr({ attribute: 'readonly', mode: 'boolean' })
348
- ], RadioGroup.prototype, "readOnly", void 0);
450
+ observable
451
+ ], RadioGroup.prototype, "checkedIndex", void 0);
349
452
  __decorate([
350
453
  attr({ attribute: 'disabled', mode: 'boolean' })
351
454
  ], RadioGroup.prototype, "disabled", void 0);
352
455
  __decorate([
353
- attr
354
- ], RadioGroup.prototype, "name", void 0);
456
+ attr({ attribute: 'value', mode: 'fromView' })
457
+ ], RadioGroup.prototype, "initialValue", void 0);
355
458
  __decorate([
356
459
  attr
357
- ], RadioGroup.prototype, "value", void 0);
460
+ ], RadioGroup.prototype, "name", void 0);
358
461
  __decorate([
359
462
  attr
360
463
  ], RadioGroup.prototype, "orientation", void 0);
361
464
  __decorate([
362
465
  observable
363
- ], RadioGroup.prototype, "childItems", void 0);
466
+ ], RadioGroup.prototype, "radios", void 0);
364
467
  __decorate([
365
- observable
366
- ], RadioGroup.prototype, "slottedRadioButtons", void 0);
468
+ attr({ mode: 'boolean' })
469
+ ], RadioGroup.prototype, "required", void 0);
367
470
  //# sourceMappingURL=radio-group.js.map