@momentum-design/components 0.133.4 → 0.133.6

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.
@@ -1,6 +1,5 @@
1
1
  import type { CSSResult } from 'lit';
2
2
  import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
3
- declare const FormfieldGroup_base: import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/DataAriaLabelMixin").DataAriaLabelMixinInterface> & typeof FormfieldWrapper;
4
3
  /**
5
4
  * `mdc-formfieldgroup` component, groups the form field components together.
6
5
  * All passed in children will have a gap of 12px (0.75rem) each applied.
@@ -38,7 +37,6 @@ declare const FormfieldGroup_base: import("../../utils/mixins/index.types").Cons
38
37
  * @csspart help-text - The helper/validation text element.
39
38
  * @csspart helper-icon - The helper/validation icon element that is displayed next to the helper/validation text.
40
39
  * @csspart help-text-container - The container for the helper/validation icon and text elements.
41
- * @csspart container - Formfieldgroup host container
42
40
  * @csspart group-header - This contains the label and help text for the group
43
41
  *
44
42
  * @cssproperty --mdc-label-font-size - Font size for the label text.
@@ -51,13 +49,14 @@ declare const FormfieldGroup_base: import("../../utils/mixins/index.types").Cons
51
49
  * @cssproperty --mdc-help-text-color - Color for the help text.
52
50
  * @cssproperty --mdc-required-indicator-color - Color for the required indicator text.
53
51
  */
54
- declare class FormfieldGroup extends FormfieldGroup_base {
52
+ declare class FormfieldGroup extends FormfieldWrapper {
55
53
  /**
56
54
  * @internal
57
55
  * This is used to set the role of the component as `radiogroup` if this is true and to 'group' if it is false.
58
56
  */
59
57
  protected isRadio: boolean;
60
58
  connectedCallback(): void;
59
+ update(changedProperties: Map<string, unknown>): void;
61
60
  render(): import("lit-html").TemplateResult<1>;
62
61
  static styles: Array<CSSResult>;
63
62
  }
@@ -1,9 +1,6 @@
1
1
  import { html } from 'lit';
2
- import { ifDefined } from 'lit/directives/if-defined.js';
3
- import { DataAriaLabelMixin } from '../../utils/mixins/DataAriaLabelMixin';
4
2
  import { ROLE } from '../../utils/roles';
5
3
  import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
6
- import { DEFAULTS as FORMFIELD_DEFAULTS } from '../formfieldwrapper/formfieldwrapper.constants';
7
4
  import styles from './formfieldgroup.styles';
8
5
  /**
9
6
  * `mdc-formfieldgroup` component, groups the form field components together.
@@ -42,7 +39,6 @@ import styles from './formfieldgroup.styles';
42
39
  * @csspart help-text - The helper/validation text element.
43
40
  * @csspart helper-icon - The helper/validation icon element that is displayed next to the helper/validation text.
44
41
  * @csspart help-text-container - The container for the helper/validation icon and text elements.
45
- * @csspart container - Formfieldgroup host container
46
42
  * @csspart group-header - This contains the label and help text for the group
47
43
  *
48
44
  * @cssproperty --mdc-label-font-size - Font size for the label text.
@@ -55,7 +51,7 @@ import styles from './formfieldgroup.styles';
55
51
  * @cssproperty --mdc-help-text-color - Color for the help text.
56
52
  * @cssproperty --mdc-required-indicator-color - Color for the required indicator text.
57
53
  */
58
- class FormfieldGroup extends DataAriaLabelMixin(FormfieldWrapper) {
54
+ class FormfieldGroup extends FormfieldWrapper {
59
55
  constructor() {
60
56
  super(...arguments);
61
57
  /**
@@ -65,23 +61,24 @@ class FormfieldGroup extends DataAriaLabelMixin(FormfieldWrapper) {
65
61
  this.isRadio = false;
66
62
  }
67
63
  connectedCallback() {
64
+ var _a;
68
65
  super.connectedCallback();
69
66
  this.shouldRenderLabel = false;
70
67
  this.disabled = undefined;
68
+ this.role = this.isRadio ? ROLE.RADIOGROUP : ROLE.GROUP;
69
+ this.ariaDescription = (_a = this.helpText) !== null && _a !== void 0 ? _a : '';
70
+ this.ariaLabel = this.label || '';
71
+ }
72
+ update(changedProperties) {
73
+ super.update(changedProperties);
74
+ if (changedProperties.has('label') && !this.ariaLabel) {
75
+ this.ariaLabel = this.label || '';
76
+ }
71
77
  }
72
78
  render() {
73
- var _a;
74
79
  return html `
75
- <div
76
- part="container"
77
- role="${this.isRadio ? ROLE.RADIOGROUP : ROLE.GROUP}"
78
- aria-labelledby="${FORMFIELD_DEFAULTS.HEADING_ID}"
79
- aria-describedby="${ifDefined(this.helpText ? FORMFIELD_DEFAULTS.HELPER_TEXT_ID : '')}"
80
- aria-label="${(_a = this.dataAriaLabel) !== null && _a !== void 0 ? _a : ''}"
81
- >
82
- <div part="group-header">${this.renderLabel()} ${this.renderHelperText()}</div>
83
- <slot></slot>
84
- </div>
80
+ <div part="group-header">${this.renderLabel()} ${this.renderHelperText()}</div>
81
+ <slot></slot>
85
82
  `;
86
83
  }
87
84
  }
@@ -1,6 +1,6 @@
1
1
  import { css } from 'lit';
2
2
  const styles = css `
3
- :host::part(container) {
3
+ :host {
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  gap: 0.75rem;
@@ -1,7 +1,7 @@
1
1
  import { CSSResult, PropertyValueMap, PropertyValues } from 'lit';
2
2
  import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
3
3
  import { AssociatedFormControl } from '../../utils/mixins/FormInternalsMixin';
4
- declare const Radio_base: import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/KeyDownHandledMixin").KeyDownHandledMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/KeyToActionMixin").KeyToActionInterface> & import("../../utils/mixins/index.types").Constructor<import("../../models").Component & import("../../utils/mixins/AutoFocusOnMountMixin").AutoFocusOnMountMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/FormInternalsMixin").FormInternalsMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/DataAriaLabelMixin").DataAriaLabelMixinInterface> & typeof FormfieldWrapper;
4
+ declare const Radio_base: import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/KeyDownHandledMixin").KeyDownHandledMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/KeyToActionMixin").KeyToActionInterface> & import("../../utils/mixins/index.types").Constructor<import("../../models").Component & import("../../utils/mixins/AutoFocusOnMountMixin").AutoFocusOnMountMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/FormInternalsMixin").FormInternalsMixinInterface> & typeof FormfieldWrapper;
5
5
  /**
6
6
  * The Radio component allows users to select a single option from a group of mutually exclusive choices.
7
7
  * Unlike checkboxes which allow multiple selections, radio buttons ensure only one option can be selected
@@ -11,19 +11,26 @@ declare const Radio_base: import("../../utils/mixins/index.types").Constructor<i
11
11
  * To create a group of radio buttons, use the `mdc-radiogroup` component or ensure all radio buttons
12
12
  * share the same `name` attribute.
13
13
  *
14
- * **Note:** This component internally renders a native radio input element with custom styling.
14
+ * ## Validation
15
15
  *
16
- * ## When to use
17
- * Use radio buttons when users must select exactly one option from a list of 2-5 choices. For longer lists,
18
- * consider using a dropdown menu instead.
16
+ * Radio component support native form validation. But it does not have default validation message.
17
+ * Also, `required` attribute does not render indicator (red asterisk) for the radio component.
18
+ *
19
+ * The recommended way to show validation message for radio groups is to wrap the `mdc-radio` with `mdc-radiogroup`
20
+ * and set the `help-text` of the `mdc-radiogroup` based on its validation state.
21
+ *
22
+ * Alternatively you can also set the `validation-message` attribute of the `mdc-radio`. This message will appear
23
+ * in a native tooltip when the radio is checked and invalid.
19
24
  *
20
25
  * ## Accessibility
26
+ *
21
27
  * - Provide clear labels that describe each option
22
28
  * - Use `data-aria-label` when a visual label is not present
23
29
  * - Keyboard navigation: Arrow keys to move between options, Space to select, Tab to navigate groups, Enter to submit form
24
30
  * - Group related radio buttons using the same `name` attribute or `mdc-radiogroup` component
25
31
  *
26
32
  * ## Styling
33
+ *
27
34
  * Use the `static-radio` part to apply custom styles to the radio visual element.
28
35
  * This part exposes the underlying [StaticRadio](?path=/docs/components-decorator-staticradio--docs) component for advanced styling.
29
36
  *
@@ -35,20 +42,16 @@ declare const Radio_base: import("../../utils/mixins/index.types").Constructor<i
35
42
  *
36
43
  * @tagname mdc-radio
37
44
  *
38
- * @event change - (React: onChange) Event that gets dispatched when the radio state changes.
45
+ * @event input - (React: onInput) Event that gets dispatched when the radio state changes (before the change event).
46
+ * @event change - (React: onChange) Event that gets dispatched when the radio state changes (after the input event).
39
47
  * @event focus - (React: onFocus) Event that gets dispatched when the radio receives focus.
40
48
  *
41
49
  * @csspart label - The label element.
42
50
  * @csspart label-text - The container for the label and required indicator elements.
43
- * @csspart required-indicator - The required indicator element that is displayed next to the label when the `required` property is set to true.
44
- * @csspart info-icon-btn - The info icon button element that is displayed next to the label when the `toggletip-text` property is set.
45
- * @csspart label-toggletip - The toggletip element that is displayed when the info icon button is clicked.
46
- * @csspart help-text - The helper/validation text element.
47
- * @csspart helper-icon - The helper/validation icon element that is displayed next to the helper/validation text.
48
- * @csspart help-text-container - The container for the helper/validation icon and text elements.
49
- * @csspart radio-input - The native radio input element that provides the interactive functionality.
50
- * @csspart text-container - The container for the label and helper text elements.
51
51
  * @csspart static-radio - The staticradio that provides the visual radio appearance.
52
+ *
53
+ * @slot indicator - Slot for the radio indicator element. If not provided, a default styled radio will be rendered.
54
+ * @slot label - Slot for the label of the radio.
52
55
  */
53
56
  declare class Radio extends Radio_base implements AssociatedFormControl {
54
57
  /**
@@ -57,6 +60,7 @@ declare class Radio extends Radio_base implements AssociatedFormControl {
57
60
  * @default false
58
61
  */
59
62
  checked: boolean;
63
+ constructor();
60
64
  connectedCallback(): void;
61
65
  protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
62
66
  /**
@@ -92,6 +96,7 @@ declare class Radio extends Radio_base implements AssociatedFormControl {
92
96
  * Handles the change event on the radio element.
93
97
  * Unchecks all other radios in the same group and checks this radio.
94
98
  * Dispatches the change event.
99
+ * @param emitClick - A boolean when click event must be emitted before other events
95
100
  * @internal
96
101
  */
97
102
  private handleChange;
@@ -104,6 +109,8 @@ declare class Radio extends Radio_base implements AssociatedFormControl {
104
109
  * @internal
105
110
  */
106
111
  private updateRadio;
112
+ private handleClick;
113
+ private emitCheckedChangeEvent;
107
114
  /**
108
115
  * Handles the keydown event on the radio element.
109
116
  * Supports Arrow keys for navigation between radios in the same group, Space for selection, and Enter for form submission.
@@ -120,8 +127,12 @@ declare class Radio extends Radio_base implements AssociatedFormControl {
120
127
  */
121
128
  private updateTabIndex;
122
129
  update(changedProperties: PropertyValues): void;
123
- /** @internal */
124
- private renderLabelAndHelperText;
130
+ /**
131
+ * Updates the aria-label of the radio element based on the label or data-aria-label attribute.
132
+ * @internal
133
+ */
134
+ private updateAriaLabel;
135
+ setValidity(): void;
125
136
  render(): import("lit-html").TemplateResult<1>;
126
137
  static styles: Array<CSSResult>;
127
138
  }
@@ -8,13 +8,10 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  /* eslint-disable no-param-reassign */
11
- import { html, nothing } from 'lit';
11
+ import { html } from 'lit';
12
12
  import { property } from 'lit/decorators.js';
13
- import { ifDefined } from 'lit/directives/if-defined.js';
14
13
  import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
15
- import { DataAriaLabelMixin } from '../../utils/mixins/DataAriaLabelMixin';
16
14
  import { FormInternalsMixin } from '../../utils/mixins/FormInternalsMixin';
17
- import { DEFAULTS as FORMFIELD_DEFAULTS } from '../formfieldwrapper/formfieldwrapper.constants';
18
15
  import { ROLE } from '../../utils/roles';
19
16
  import { AutoFocusOnMountMixin } from '../../utils/mixins/AutoFocusOnMountMixin';
20
17
  import { ACTIONS, KeyToActionMixin, NAV_MODES } from '../../utils/mixins/KeyToActionMixin';
@@ -29,19 +26,26 @@ import styles from './radio.styles';
29
26
  * To create a group of radio buttons, use the `mdc-radiogroup` component or ensure all radio buttons
30
27
  * share the same `name` attribute.
31
28
  *
32
- * **Note:** This component internally renders a native radio input element with custom styling.
29
+ * ## Validation
33
30
  *
34
- * ## When to use
35
- * Use radio buttons when users must select exactly one option from a list of 2-5 choices. For longer lists,
36
- * consider using a dropdown menu instead.
31
+ * Radio component support native form validation. But it does not have default validation message.
32
+ * Also, `required` attribute does not render indicator (red asterisk) for the radio component.
33
+ *
34
+ * The recommended way to show validation message for radio groups is to wrap the `mdc-radio` with `mdc-radiogroup`
35
+ * and set the `help-text` of the `mdc-radiogroup` based on its validation state.
36
+ *
37
+ * Alternatively you can also set the `validation-message` attribute of the `mdc-radio`. This message will appear
38
+ * in a native tooltip when the radio is checked and invalid.
37
39
  *
38
40
  * ## Accessibility
41
+ *
39
42
  * - Provide clear labels that describe each option
40
43
  * - Use `data-aria-label` when a visual label is not present
41
44
  * - Keyboard navigation: Arrow keys to move between options, Space to select, Tab to navigate groups, Enter to submit form
42
45
  * - Group related radio buttons using the same `name` attribute or `mdc-radiogroup` component
43
46
  *
44
47
  * ## Styling
48
+ *
45
49
  * Use the `static-radio` part to apply custom styles to the radio visual element.
46
50
  * This part exposes the underlying [StaticRadio](?path=/docs/components-decorator-staticradio--docs) component for advanced styling.
47
51
  *
@@ -53,49 +57,46 @@ import styles from './radio.styles';
53
57
  *
54
58
  * @tagname mdc-radio
55
59
  *
56
- * @event change - (React: onChange) Event that gets dispatched when the radio state changes.
60
+ * @event input - (React: onInput) Event that gets dispatched when the radio state changes (before the change event).
61
+ * @event change - (React: onChange) Event that gets dispatched when the radio state changes (after the input event).
57
62
  * @event focus - (React: onFocus) Event that gets dispatched when the radio receives focus.
58
63
  *
59
64
  * @csspart label - The label element.
60
65
  * @csspart label-text - The container for the label and required indicator elements.
61
- * @csspart required-indicator - The required indicator element that is displayed next to the label when the `required` property is set to true.
62
- * @csspart info-icon-btn - The info icon button element that is displayed next to the label when the `toggletip-text` property is set.
63
- * @csspart label-toggletip - The toggletip element that is displayed when the info icon button is clicked.
64
- * @csspart help-text - The helper/validation text element.
65
- * @csspart helper-icon - The helper/validation icon element that is displayed next to the helper/validation text.
66
- * @csspart help-text-container - The container for the helper/validation icon and text elements.
67
- * @csspart radio-input - The native radio input element that provides the interactive functionality.
68
- * @csspart text-container - The container for the label and helper text elements.
69
66
  * @csspart static-radio - The staticradio that provides the visual radio appearance.
67
+ *
68
+ * @slot indicator - Slot for the radio indicator element. If not provided, a default styled radio will be rendered.
69
+ * @slot label - Slot for the label of the radio.
70
70
  */
71
- class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper))))) {
71
+ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(FormInternalsMixin(FormfieldWrapper)))) {
72
72
  constructor() {
73
- super(...arguments);
73
+ super();
74
74
  /**
75
75
  * Determines whether the radio is checked (selected) or unchecked.
76
76
  * Within a radio group, only one radio can be checked at a time.
77
77
  * @default false
78
78
  */
79
79
  this.checked = false;
80
- /** @internal */
81
- this.renderLabelAndHelperText = () => {
82
- if (!this.label)
83
- return nothing;
84
- return html `<div part="text-container">${this.renderLabel()} ${this.renderHelperText()}</div>`;
85
- };
80
+ this.addEventListener('click', this.handleClick);
81
+ this.addEventListener('keydown', this.handleKeyDown);
86
82
  }
87
83
  connectedCallback() {
88
84
  super.connectedCallback();
89
- // Radio does not contain helpTextType property.
85
+ this.role = ROLE.RADIO;
86
+ this.shouldRenderLabel = false;
87
+ // Radio should not contain these properties.
90
88
  this.helpTextType = undefined;
89
+ this.toggletipPlacement = undefined;
90
+ this.toggletipStrategy = undefined;
91
+ this.updateAriaLabel();
91
92
  }
92
93
  firstUpdated(_changedProperties) {
93
94
  this.updateTabIndex();
94
95
  // set the element to auto focus if autoFocusOnMount is set to true
95
96
  // before running the super method, so that the AutoFocusOnMountMixin can use it
96
97
  // to focus the correct element
97
- if (this.inputElement && this.autoFocusOnMount) {
98
- this.elementToAutoFocus = this.inputElement;
98
+ if (this.autoFocusOnMount) {
99
+ this.elementToAutoFocus = this;
99
100
  }
100
101
  super.firstUpdated(_changedProperties);
101
102
  }
@@ -138,12 +139,6 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
138
139
  this.internals.setValidity({});
139
140
  }
140
141
  else if (this.required && !this.checked) {
141
- if (this.validationMessage) {
142
- this.inputElement.setCustomValidity(this.validationMessage);
143
- }
144
- else {
145
- this.inputElement.setCustomValidity('');
146
- }
147
142
  this.setValidity();
148
143
  }
149
144
  this.updateTabIndex();
@@ -157,15 +152,9 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
157
152
  setGroupValidity(radios, isValid) {
158
153
  this.updateComplete
159
154
  .then(() => {
160
- radios.forEach(radio => {
161
- radio.setComponentValidity(isValid);
162
- });
155
+ radios.forEach(radio => radio.setComponentValidity(isValid));
163
156
  })
164
- .catch(error => {
165
- if (this.onerror) {
166
- this.onerror(error);
167
- }
168
- });
157
+ .catch(error => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); });
169
158
  }
170
159
  /**
171
160
  * Updates the form value to reflect the current state of the radio.
@@ -174,57 +163,43 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
174
163
  * @internal
175
164
  */
176
165
  setActualFormValue() {
177
- let actualValue = '';
178
- if (this.checked) {
179
- actualValue = !this.value ? 'on' : this.value;
180
- }
181
- else {
182
- actualValue = null;
183
- }
184
166
  const radios = this.getAllRadiosWithinSameGroup();
185
167
  if (this.checked) {
186
168
  this.setGroupValidity(radios, true);
169
+ this.internals.setFormValue(this.value || 'on');
187
170
  }
188
171
  else {
189
172
  const anyRequired = radios.some(r => r.required);
190
- const anyChecked = !!radios.find(r => r.checked);
173
+ const anyChecked = radios.some(r => r.checked);
191
174
  const isInvalid = anyRequired && !anyChecked;
192
175
  this.setGroupValidity(radios, !isInvalid);
176
+ this.internals.setFormValue(null);
193
177
  }
194
- this.internals.setFormValue(actualValue);
195
178
  }
196
179
  /**
197
180
  * Handles the change event on the radio element.
198
181
  * Unchecks all other radios in the same group and checks this radio.
199
182
  * Dispatches the change event.
183
+ * @param emitClick - A boolean when click event must be emitted before other events
200
184
  * @internal
201
185
  */
202
- handleChange() {
203
- var _a;
204
- if (this.disabled || this.readonly || this.softDisabled)
186
+ handleChange(emitClick = false) {
187
+ if (this.checked || this.disabled || this.readonly || this.softDisabled)
205
188
  return;
206
189
  const radios = this.getAllRadiosWithinSameGroup();
190
+ // Uncheck all radios in the same group (name)
207
191
  radios.forEach(radio => {
208
- var _a;
209
- /**
210
- * Uncheck all radios in the same group (name)
211
- */
212
- const radioElement = (_a = radio.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input');
213
- if (radioElement) {
214
- radio.checked = false;
215
- radioElement.checked = false;
216
- }
192
+ radio.checked = false;
217
193
  });
218
194
  this.checked = true;
219
- const inputElement = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input');
220
- if (inputElement) {
221
- inputElement.checked = true;
195
+ // Native radio input emits click, input and change event in this order when
196
+ if (emitClick) {
197
+ super.click();
222
198
  }
223
- this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
199
+ this.emitCheckedChangeEvent();
224
200
  }
225
201
  click() {
226
- super.click();
227
- this.handleChange();
202
+ this.handleChange(true);
228
203
  }
229
204
  /**
230
205
  * Updates the state of the radio button at the specified index within the enabled radios.
@@ -234,9 +209,18 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
234
209
  * @internal
235
210
  */
236
211
  updateRadio(enabledRadios, index) {
237
- var _a, _b;
238
- (_b = (_a = enabledRadios[index].shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input')) === null || _b === void 0 ? void 0 : _b.focus();
239
- enabledRadios[index].handleChange();
212
+ const radio = enabledRadios[index];
213
+ radio.focus();
214
+ radio.handleChange();
215
+ }
216
+ handleClick() {
217
+ this.handleChange();
218
+ }
219
+ emitCheckedChangeEvent() {
220
+ if (this.checked) {
221
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
222
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
223
+ }
240
224
  }
241
225
  /**
242
226
  * Handles the keydown event on the radio element.
@@ -256,19 +240,12 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
256
240
  const enabledRadios = radios.filter(radio => !radio.disabled);
257
241
  const currentIndex = enabledRadios.indexOf(this);
258
242
  // Leave navigation between radios to the spatial navigation context if it exists
259
- if (this.getKeyboardNavMode() !== NAV_MODES.DEFAULT) {
243
+ if (this.getKeyboardNavMode() === NAV_MODES.SPATIAL) {
260
244
  if (action === ACTIONS.ENTER) {
261
245
  this.updateRadio(enabledRadios, currentIndex);
262
246
  this.keyDownEventHandled();
263
247
  }
264
- const radios = this.getAllRadiosWithinSameGroup();
265
- radios.forEach(radio => {
266
- var _a;
267
- const input = (_a = radio.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input');
268
- if (input) {
269
- input.tabIndex = 0;
270
- }
271
- });
248
+ this.updateTabIndex();
272
249
  return;
273
250
  }
274
251
  if (action === ACTIONS.DOWN || action === ACTIONS.RIGHT) {
@@ -297,60 +274,66 @@ class Radio extends KeyDownHandledMixin(KeyToActionMixin(AutoFocusOnMountMixin(F
297
274
  * @internal
298
275
  */
299
276
  updateTabIndex() {
277
+ // Leave navigation between radios to the spatial navigation context if it exists
278
+ const isSpatialNavMode = this.getKeyboardNavMode() === NAV_MODES.SPATIAL;
300
279
  const radios = this.getAllRadiosWithinSameGroup();
301
280
  const checked = radios.find(radio => radio.checked);
302
281
  const firstEnabledRadio = radios.find(radio => !radio.disabled);
303
282
  radios.forEach(radio => {
304
- var _a;
305
- const inputElement = (_a = radio.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input');
306
- if (inputElement) {
307
- inputElement.tabIndex = -1;
308
- if (radio === checked) {
309
- inputElement.tabIndex = 0;
310
- }
311
- else if (!checked && radio === firstEnabledRadio) {
312
- inputElement.tabIndex = 0;
313
- }
283
+ radio.tabIndex = -1;
284
+ if (isSpatialNavMode) {
285
+ radio.tabIndex = 0;
286
+ }
287
+ else if (radio === checked) {
288
+ radio.tabIndex = 0;
289
+ }
290
+ else if (!checked && radio === firstEnabledRadio) {
291
+ radio.tabIndex = 0;
314
292
  }
315
293
  });
316
294
  }
317
295
  update(changedProperties) {
318
296
  super.update(changedProperties);
319
297
  if (changedProperties.has('checked')) {
298
+ this.ariaChecked = this.checked.toString();
320
299
  this.setActualFormValue();
321
300
  }
301
+ if (changedProperties.has('label')) {
302
+ this.updateAriaLabel();
303
+ }
304
+ }
305
+ /**
306
+ * Updates the aria-label of the radio element based on the label or data-aria-label attribute.
307
+ * @internal
308
+ */
309
+ updateAriaLabel() {
310
+ if (!this.ariaLabel && this.ariaLabel !== this.label) {
311
+ this.ariaLabel = this.label || '';
312
+ }
313
+ }
314
+ setValidity() {
315
+ if (this.required && !this.checked && this.validationMessage) {
316
+ this.internals.setValidity({ valueMissing: true, customError: true }, this.validationMessage, this);
317
+ }
318
+ this.internals.setValidity({});
322
319
  }
323
320
  render() {
324
- var _a;
325
321
  return html `
326
- <mdc-staticradio
327
- class="mdc-focus-ring"
328
- ?checked="${this.checked}"
329
- ?disabled="${this.disabled}"
330
- ?readonly="${this.readonly}"
331
- ?soft-disabled="${this.softDisabled}"
332
- part="static-radio"
333
- >
334
- <input
335
- id="${this.inputId}"
336
- part="radio-input"
337
- type="radio"
338
- role="${ROLE.RADIO}"
339
- ?autofocus="${this.autofocus}"
340
- name="${ifDefined(this.name)}"
341
- value="${ifDefined(this.value)}"
342
- ?required="${this.required}"
343
- @change=${this.handleChange}
344
- @keydown=${this.handleKeyDown}
345
- ?checked=${this.checked}
346
- ?readonly=${this.readonly}
347
- ?disabled=${this.disabled}
348
- aria-checked="${this.checked}"
349
- aria-describedby="${ifDefined(this.helpText ? FORMFIELD_DEFAULTS.HELPER_TEXT_ID : '')}"
350
- aria-label="${(_a = this.dataAriaLabel) !== null && _a !== void 0 ? _a : ''}"
351
- />
352
- </mdc-staticradio>
353
- ${this.renderLabelAndHelperText()}
322
+ <slot name="indicator">
323
+ <mdc-staticradio
324
+ part="radio-indicator"
325
+ role="presentation"
326
+ class="mdc-focus-ring"
327
+ ?checked="${this.checked}"
328
+ ?disabled="${this.disabled}"
329
+ ?readonly="${this.readonly}"
330
+ ?soft-disabled="${this.softDisabled}"
331
+ >
332
+ </mdc-staticradio>
333
+ </slot>
334
+ <div part="label-text">
335
+ <slot name="label">${this.renderLabelElement()}</slot>
336
+ </div>
354
337
  `;
355
338
  }
356
339
  }
@@ -1,5 +1,5 @@
1
1
  import { css } from 'lit';
2
- import { hostFitContentStyles, hostFocusRingStyles } from '../../utils/styles';
2
+ import { focusRingBoxShadow, hostFitContentStyles, hostFocusRingStyles } from '../../utils/styles';
3
3
  const styles = [
4
4
  hostFitContentStyles,
5
5
  css `
@@ -13,22 +13,8 @@ const styles = [
13
13
  gap: 0.5rem;
14
14
  }
15
15
 
16
- :host::part(radio-input) {
17
- position: absolute;
18
- opacity: 0;
19
- margin: 0;
20
- width: 100%;
21
- height: 100%;
22
- cursor: pointer;
23
- z-index: 1;
24
- }
25
-
26
- :host::part(text-container) {
27
- display: flex;
28
- flex-direction: column;
29
- justify-content: center;
30
- gap: 0.25rem;
31
- flex: 1;
16
+ :host::part(radio-indicator) {
17
+ flex: none;
32
18
  }
33
19
 
34
20
  :host::part(label) {
@@ -37,6 +23,10 @@ const styles = [
37
23
  white-space: normal;
38
24
  }
39
25
 
26
+ :host::part(label-text) {
27
+ display: contents;
28
+ }
29
+
40
30
  :host(:hover)::part(static-radio) {
41
31
  --mdc-staticradio-outer-circle-background-color: var(--mds-color-theme-control-inactive-hover);
42
32
  }
@@ -60,6 +50,30 @@ const styles = [
60
50
  :host([soft-disabled]) {
61
51
  pointer-events: none;
62
52
  }
53
+
54
+ :host(:focus-within),
55
+ :host(:focus-visible) {
56
+ outline: none;
57
+ }
58
+
59
+ :host(:focus-visible)::part(radio-indicator) {
60
+ outline: none;
61
+ }
62
+ :host([disabled]:focus) {
63
+ box-shadow: none;
64
+ }
65
+
66
+ :host(:focus-within)::part(radio-indicator) {
67
+ position: relative;
68
+ box-shadow: ${focusRingBoxShadow};
69
+ }
70
+
71
+ /* High Contrast Mode */
72
+ @media (forced-colors: active) {
73
+ :host(:focus-visible)::part(radio-indicator) {
74
+ outline: 0.125rem solid var(--mds-color-theme-focus-default-0);
75
+ }
76
+ }
63
77
  `,
64
78
  ...hostFocusRingStyles(true),
65
79
  ];
@@ -1,7 +1,8 @@
1
- import type { OverrideEventTarget, TypedCustomEvent } from "../../utils/types";
2
- import type Radio from "./radio.component";
1
+ import type { OverrideEventTarget, TypedCustomEvent } from '../../utils/types';
2
+ import type Radio from './radio.component';
3
3
  interface Events {
4
4
  onChangeEvent: TypedCustomEvent<Radio>;
5
5
  onFocusEvent: OverrideEventTarget<FocusEvent, Radio>;
6
+ onInputEvent: OverrideEventTarget<Event, Radio>;
6
7
  }
7
8
  export type { Events };