@jack-henry/jh-elements 2.0.0-beta.11 → 2.0.0-beta.13

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.
@@ -9,10 +9,9 @@ import { JhInput } from '../input/input.js';
9
9
  * @customElement jh-input-email
10
10
  */
11
11
  export class JhInputEmail extends JhInput {
12
- firstUpdated() {
13
- super.firstUpdated();
14
- let inputEl = this.shadowRoot.querySelector('input');
15
- inputEl.setAttribute('type', 'email');
12
+ constructor() {
13
+ super();
14
+ this.inputmode = 'email';
16
15
  }
17
16
  }
18
17
  customElements.define('jh-input-email', JhInputEmail);
@@ -2,48 +2,23 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
 
5
- import { html, render, css } from 'lit';
5
+ import { html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
7
  import { JhInput } from '../input/input.js';
7
8
  import '@jack-henry/jh-icons/icons-wc/icon-eye-slash.js';
8
9
  import '@jack-henry/jh-icons/icons-wc/icon-eye.js';
9
10
 
11
+ let id = 0;
12
+
10
13
  /**
11
14
  * @slot jh-input-password-hidden - Use to insert a custom icon within the toggle password button when the input value is masked.
12
15
  * @slot jh-input-password-visible - Use to insert a custom icon within the toggle password button when the input value is unmasked.
13
16
  * @customElement jh-input-password
14
17
  */
15
18
  export class JhInputPassword extends JhInput {
16
- #inputEl;
17
-
18
- static get styles() {
19
- return [
20
- super.styles,
21
- css`
22
- .password-toggle-btn {
23
- position: absolute;
24
- right: var(--jh-dimension-400);
25
- }
26
- .clear-button {
27
- right: var(--jh-dimension-1400);
28
- }
29
- .display-clear-button input {
30
- padding-right: var(--jh-dimension-2400);
31
- }
32
- input {
33
- padding-right: var(--jh-dimension-1400);
34
- }
35
- :host([size='small']) .password-toggle-btn {
36
- top: 4px;
37
- }
38
- :host([size='medium']) .password-toggle-btn {
39
- top: 8px;
40
- }
41
- :host([size='large']) .password-toggle-btn {
42
- top: 12px;
43
- }
44
- `
45
- ];
46
- }
19
+
20
+ /** @type {?number} */
21
+ #id;
47
22
 
48
23
  static get properties() {
49
24
  return {
@@ -69,39 +44,74 @@ export class JhInputPassword extends JhInput {
69
44
  /** @type {?string} */
70
45
  this.accessibleLabelShowPassword = null;
71
46
  /** @type {boolean} */
72
- this.hideRightSlot = true;
73
- /** @type {boolean} */
74
47
  this.passwordVisible = false;
75
48
  }
76
49
 
77
- firstUpdated() {
78
- super.firstUpdated();
79
- this.#inputEl = this.shadowRoot.querySelector('input');
50
+ connectedCallback() {
51
+ super.connectedCallback();
52
+ this.#id = id++;
80
53
  }
81
54
 
82
- updated(changedProperties) {
83
- super.updated(changedProperties);
55
+ renderInput() {
56
+ let describedby;
84
57
 
85
- if (changedProperties.has('passwordVisible')) {
86
- if (this.passwordVisible) {
87
- this.#inputEl.setAttribute('type', 'text');
88
- } else {
89
- this.#inputEl.setAttribute('type', 'password');
90
- }
58
+ if (this.helperText || (this.errorText && this.invalid)) {
59
+ describedby = this._getDescribedby();
91
60
  }
92
- this.#insertTogglePasswordBtn();
61
+
62
+ const leftSlot = this.readonly ? null : this.renderLeftSlot();
63
+ const rightSlot = this.readonly ? null : this.renderRightSlot();
64
+ const clearButton = this.readonly ? null : this.renderClearButton();
65
+
66
+ return html`
67
+ <div class="input-container">
68
+ <div class="input-wrapper">
69
+ ${leftSlot}
70
+ <input
71
+ id="jh-input-${this.#id}"
72
+ aria-describedby=${describedby}
73
+ aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
74
+ aria-label=${ifDefined(
75
+ this.accessibleLabel === '' ? null : this.accessibleLabel
76
+ )}
77
+ autocomplete=${ifDefined(
78
+ this.autocomplete === '' ? null : this.autocomplete
79
+ )}
80
+ ?disabled=${this.disabled}
81
+ enterkeyhint=${ifDefined(
82
+ this.enterkeyhint === '' ? null : this.enterkeyhint
83
+ )}
84
+ inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
85
+ maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
86
+ minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
87
+ name=${ifDefined(this.name === '' ? null : this.name)}
88
+ ?readonly=${this.readonly}
89
+ ?required=${this.required}
90
+ type=${this.passwordVisible ? 'text' : 'password'}
91
+ .value=${this.value}
92
+ @keydown=${this.inputMask ? this._handleKeydown : null}
93
+ @change=${this._handleChange}
94
+ @input=${this._handleInput}
95
+ @select=${this._handleSelect}
96
+ />
97
+ ${clearButton}
98
+ ${rightSlot}
99
+ </div>
100
+ </div>
101
+ `;
93
102
  }
94
103
 
95
- #insertTogglePasswordBtn() {
96
- let inputContainerEl = this.shadowRoot.querySelector('.input-container');
97
- let togglePasswordButton = this.#createTogglePasswordBtn();
98
- render(togglePasswordButton, inputContainerEl);
104
+ renderRightSlot() {
105
+ if (this.hideRightSlot) return;
106
+
107
+ return html`
108
+ <slot name="jh-input-right" @slotchange=${this._handleSlotChange}>${this.#renderTogglePasswordBtn()}</slot>
109
+
110
+ `;
99
111
  }
100
112
 
101
- #createTogglePasswordBtn() {
102
- if (this.readonly) {
103
- return;
104
- }
113
+ #renderTogglePasswordBtn() {
114
+ if (this.readonly) return;
105
115
 
106
116
  let accessibleLabel = this.passwordVisible
107
117
  ? this.accessibleLabelHidePassword
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
 
5
- import { css } from 'lit';
5
+ import { css, html } from 'lit';
6
6
  import { JhInput } from '../input/input.js';
7
7
  import '@jack-henry/jh-icons/icons-wc/icon-magnifying-glass.js';
8
8
 
@@ -25,17 +25,20 @@ export class JhInputSearch extends JhInput {
25
25
 
26
26
  firstUpdated() {
27
27
  super.firstUpdated();
28
- let leftSlot = this.shadowRoot.querySelector('slot[name="jh-input-left"]');
29
-
30
- // insert fallback content in left slot if empty
31
- if (leftSlot && leftSlot.assignedElements().length === 0) {
32
- this.innerHTML +=
33
- '<jh-icon-magnifying-glass slot="jh-input-left" aria-hidden="true"></jh-icon-magnifying-glass>';
34
- }
35
28
 
36
29
  // set input type to search
37
30
  let inputEl = this.shadowRoot.querySelector('input');
38
31
  inputEl.setAttribute('type', 'search');
39
32
  }
33
+
34
+ renderLeftSlot() {
35
+ if (this.hideLeftSlot) return null;
36
+
37
+ return html`
38
+ <slot name="jh-input-left" @slotchange=${this._handleSlotChange}>
39
+ <jh-icon-magnifying-glass aria-hidden="true"></jh-icon-magnifying-glass>
40
+ </slot>
41
+ `;
42
+ }
40
43
  }
41
44
  customElements.define('jh-input-search', JhInputSearch);
@@ -18,6 +18,16 @@ let id = 0;
18
18
  export class JhInputTextarea extends JhInput {
19
19
  /** @type {?number} */
20
20
  #id;
21
+ /** @type {?ResizeObserver} */
22
+ #resizeObserver;
23
+
24
+ get #textareaEl() {
25
+ return this.renderRoot?.querySelector('textarea');
26
+ }
27
+
28
+ get #footerContent() {
29
+ return this.renderRoot?.querySelector('.footer-content');
30
+ }
21
31
 
22
32
  static get styles() {
23
33
  return [
@@ -118,6 +128,7 @@ export class JhInputTextarea extends JhInput {
118
128
  );
119
129
  }
120
130
  :host([readonly]) textarea {
131
+ background-color: transparent;
121
132
  height: auto;
122
133
  border: none;
123
134
  padding-left: 0;
@@ -153,14 +164,14 @@ export class JhInputTextarea extends JhInput {
153
164
  attribute: 'auto-grow',
154
165
  },
155
166
  /** Sets the width of the input field. */
156
- cols: { type: String },
167
+ cols: { type: Number },
157
168
  /** Removes native resize capability of the input field. */
158
169
  noResize: {
159
170
  type: Boolean,
160
171
  attribute: 'no-resize',
161
172
  },
162
173
  /** Sets the height of the input field. */
163
- rows: { type: String },
174
+ rows: { type: Number },
164
175
  /** Specifies how text should be wrapped when submitted in a form. The `cols` property must be set for `wrap='hard'` to take effect. */
165
176
  wrap: { type: String },
166
177
  /** Prevents users from changing the input value. */
@@ -171,12 +182,12 @@ export class JhInputTextarea extends JhInput {
171
182
  constructor() {
172
183
  super();
173
184
  /** @type {boolean} */
174
- this.autoGrow = null;
175
- /** @type {?string} */
185
+ this.autoGrow = false;
186
+ /** @type {?number} */
176
187
  this.cols = null;
177
188
  /** @type {boolean} */
178
189
  this.noResize = true;
179
- /** @type {?string} */
190
+ /** @type {?number} */
180
191
  this.rows = null;
181
192
  /** @type {'small'|'medium'|'large'} */
182
193
  this.size = 'medium';
@@ -189,191 +200,72 @@ export class JhInputTextarea extends JhInput {
189
200
  this.#id = id++;
190
201
  }
191
202
 
192
- firstUpdated() {
193
- // add resize observer to update width of footer when textarea width changes
194
- let footerContent = this.shadowRoot.querySelector('.footer-content');
195
- let textareaEl = this.shadowRoot.querySelector('textarea');
196
-
197
- if (footerContent) {
198
- new ResizeObserver(() => {
199
- this.#updateFooterWidth(footerContent, textareaEl);
200
- }).observe(textareaEl);
203
+ disconnectedCallback() {
204
+ super.disconnectedCallback();
205
+ if (this.#resizeObserver) {
206
+ this.#resizeObserver.unobserve(this.#textareaEl);
207
+ this.#resizeObserver.disconnect();
208
+ this.#resizeObserver = null;
201
209
  }
202
210
  }
203
211
 
204
- #updateFooterWidth(footerContent, textareaEl) {
205
- footerContent.style.width = textareaEl.offsetWidth + 'px';
212
+ firstUpdated() {
213
+ // add resize observer to update width of footer when textarea width changes
214
+ if (this.#footerContent) {
215
+ this.#resizeObserver = new ResizeObserver(() => {
216
+ this.#updateFooterWidth();
217
+ });
218
+ this.#resizeObserver.observe(this.#textareaEl);
219
+ }
206
220
  }
207
221
 
208
- #dispatch(eventName, details) {
209
- this.dispatchEvent(
210
- new CustomEvent(eventName, {
211
- detail: details,
212
- bubbles: true,
213
- cancelable: true,
214
- composed: true,
215
- })
216
- );
222
+ #updateFooterWidth() {
223
+ if (this.#footerContent) {
224
+ this.#footerContent.style.width = this.#textareaEl.offsetWidth + 'px';
225
+ }
217
226
  }
218
227
 
219
- #handleInput(e) {
220
- this.value = e.target.value;
221
-
222
- this.#dispatch('jh-input', { value: this.value } );
223
-
228
+ _handleInput(e) {
229
+ super._handleInput(e);
230
+
231
+ // add textarea specific autogrow
224
232
  if (this.autoGrow) {
225
233
  this.#autoGrowTextarea();
226
234
  }
227
235
  }
228
236
 
229
237
  #autoGrowTextarea() {
230
- let textareaEl = this.shadowRoot.querySelector('textarea');
231
- textareaEl.style.height = 'auto';
232
- textareaEl.style.height = textareaEl.scrollHeight + 'px';
233
- }
234
-
235
- #handleChange() {
236
- let payload = {
237
- 'value': this.value,
238
- };
239
- this.#dispatch('jh-change', payload);
238
+ // Reset height to auto to get the correct scrollHeight
239
+ this.#textareaEl.style.height = 'auto';
240
+ // Set height to scrollHeight to accommodate content
241
+ this.#textareaEl.style.height = `${this.#textareaEl.scrollHeight}px`;
240
242
  }
241
243
 
242
- #handleSelect(e) {
243
- const selectedString = e.target.value.substring(
244
- e.target.selectionStart,
245
- e.target.selectionEnd
246
- );
247
-
248
- if (selectedString) {
249
- this.#dispatch('jh-select', {
250
- selected: selectedString,
251
- selectionStart: e.target.selectionStart,
252
- selectionEnd: e.target.selectionEnd,
253
- });
254
- }
255
- }
256
-
257
- #handleMaxlength() {
258
- this.#dispatch('jh-maxlength');
259
- }
260
-
261
- #getDescribedby() {
262
- let describedbyString = '';
263
-
264
- if (this.errorText) {
265
- describedbyString += `jh-input-error-${this.#id}`;
266
- }
267
- if (this.helperText) {
268
- describedbyString += ` jh-input-helper-${this.#id}`;
269
- }
270
- if (this.showCharCount) {
271
- describedbyString += ` jh-input-counter-${this.#id}`;
272
- }
273
- return describedbyString;
274
- }
275
-
276
- render() {
277
- let label;
278
- let indicator;
279
- let helperText;
280
- let input;
281
- let footer;
282
- let errorText;
283
- let charCount;
284
- let describedby;
285
-
286
- if (this.label) {
287
- if (this.showIndicator) {
288
- if (this.required) {
289
- indicator = html`<span class="indicator" aria-hidden="true"> *</span>`;
290
- } else {
291
- indicator = html`<span class="indicator"> (optional)</span>`;
292
- }
293
- }
294
-
295
- if (this.helperText) {
296
- helperText = html`
297
- <p id="jh-input-helper-${this.#id}" class="helper-text">
298
- ${this.helperText}
299
- </p>
300
- `;
301
- }
302
-
303
- label = html`
304
- <label for="jh-input-${this.#id}">${this.label}${indicator}</label>
305
- ${helperText}
306
- `;
307
- }
308
-
309
- if (this.showCharCount) {
310
- let valueLength = this.value ? this.value.length : 0;
311
- let charCountValue = `${this.maxlength ? `${valueLength}/${this.maxlength}` : valueLength}`;
312
-
313
- if (valueLength && valueLength === Number(this.maxlength)) {
314
- this.#handleMaxlength();
315
- }
316
-
317
- charCount = html`
318
- <p class="counter" aria-hidden="true">${charCountValue}</p>
319
- `;
320
- }
321
-
322
- if (this.invalid && this.errorText) {
323
- errorText = html`
324
- <p id="jh-input-error-${this.#id}" class="error-text">
325
- ${this.errorText}
326
- </p>
327
- `;
328
- }
329
-
330
- if ((this.invalid && this.errorText) || this.showCharCount) {
331
- footer = html`
332
- <div class="footer-content">
333
- ${errorText}
334
- ${charCount}
335
- </div>
336
- `;
337
- }
338
-
339
- if (helperText || errorText || charCount) {
340
- describedby = this.#getDescribedby();
341
- }
342
-
343
- input = html`
344
- <textarea
345
- id="jh-input-${this.#id}"
346
- aria-describedby=${describedby}
347
- aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
348
- aria-label=${ifDefined(
349
- this.accessibleLabel === '' ? null : this.accessibleLabel
350
- )}
351
- autocomplete=${ifDefined(
352
- this.autocomplete === '' ? null : this.autocomplete
353
- )}
354
- cols=${ifDefined(this.cols === '' ? null : this.cols)}
355
- ?disabled=${this.disabled}
356
- enterkeyhint=${ifDefined(
357
- this.enterkeyhint === '' ? null : this.enterkeyhint
358
- )}
359
- inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
360
- maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
361
- minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
362
- name=${ifDefined(this.name === '' ? null : this.name)}
363
- ?readonly=${this.readonly}
364
- ?required=${this.required}
365
- rows=${ifDefined(this.rows === '' ? null : this.rows)}
366
- .value=${this.value}
367
- wrap=${ifDefined(this.wrap === '' ? null : this.wrap)}
368
- @change=${this.#handleChange}
369
- @select=${this.#handleSelect}
370
- @input=${this.#handleInput}
371
- ></textarea>
372
- `;
373
-
244
+ renderInput() {
374
245
  return html`
375
- ${label} ${input} ${footer}
376
- `;
246
+ <textarea
247
+ id="jh-input-${this.#id}"
248
+ aria-describedby=${this._getDescribedby()}
249
+ aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
250
+ aria-label=${ifDefined(this.accessibleLabel === '' ? null : this.accessibleLabel)}
251
+ autocomplete=${ifDefined(this.autocomplete === '' ? null : this.autocomplete)}
252
+ cols=${ifDefined(this.cols ? Number(this.cols) : null)}
253
+ ?disabled=${this.disabled}
254
+ enterkeyhint=${ifDefined(this.enterkeyhint || null)}
255
+ inputmode=${ifDefined(this.inputmode || null)}
256
+ maxlength=${ifDefined(this.maxlength ? Number(this.maxlength) : null)}
257
+ minlength=${ifDefined(this.minlength ? Number(this.minlength) : null)}
258
+ name=${ifDefined(this.name || null)}
259
+ ?readonly=${this.readonly}
260
+ ?required=${this.required}
261
+ rows=${ifDefined(this.rows ? Number(this.rows) : null)}
262
+ .value=${this.value}
263
+ wrap=${ifDefined(this.wrap || null)}
264
+ @change=${this._handleChange}
265
+ @input=${this._handleInput}
266
+ @select=${this._handleSelect}
267
+ ></textarea>
268
+ `
377
269
  }
378
270
  }
379
- customElements.define('jh-input-textarea', JhInputTextarea);
271
+ customElements.define('jh-input-textarea', JhInputTextarea);
@@ -120,7 +120,10 @@ export class JhListItem extends LitElement {
120
120
  border-left-width: var(--jh-border-selected-width);
121
121
  }
122
122
  :host([tabindex][selected]) .list-item {
123
- padding-left: var(--jh-dimension-400);
123
+ padding-left: calc(var(
124
+ --jh-list-item-space-padding-left,
125
+ var(--jh-dimension-600)
126
+ ) - var(--jh-border-selected-width));
124
127
  }
125
128
  :host([tabindex][disabled]) {
126
129
  opacity: var(--jh-opacity-disabled);
@@ -45,6 +45,13 @@ export class JhMenu extends LitElement {
45
45
  display: flex;
46
46
  flex-direction: column;
47
47
  position: relative;
48
+ overflow: hidden;
49
+ height: 100%;
50
+ }
51
+ .menu-content {
52
+ flex: 1;
53
+ overflow-y: auto;
54
+ width: 100%;
48
55
  }
49
56
  `;
50
57
  }
@@ -54,7 +61,11 @@ export class JhMenu extends LitElement {
54
61
  this.#internals.role = 'menu';
55
62
  }
56
63
  render() {
57
- return html`<slot></slot>`;
64
+ return html`
65
+ <div class="menu-content">
66
+ <slot></slot>
67
+ </div>
68
+ `;
58
69
  }
59
70
  }
60
71
  customElements.define('jh-menu', JhMenu);
@@ -17,6 +17,7 @@ let id = 0;
17
17
  * Defaults to `--jh-color-content-secondary-enabled`.
18
18
  * @cssprop --jh-radio-group-error-color-text - The error-text text color.
19
19
  * Defaults to `--jh-color-content-negative-enabled`.
20
+ * @cssprop --jh-radio-group-opacity-disabled - The opacity of the radio group when disabled. Defaults to `--jh-opacity-disabled`.
20
21
  *
21
22
  * @slot default - Use to insert `<jh-radio>` components(s).
22
23
  *
@@ -39,10 +40,14 @@ export class JhRadioGroup extends LitElement {
39
40
  static get styles() {
40
41
  return css`
41
42
  :host {
42
- font-family: var(--jh-font-helper-regular-font-family);
43
- font-weight: var(--jh-font-helper-regular-font-weight);
44
- font-size: var(--jh-font-helper-regular-font-size);
45
- line-height: var(--jh-font-helper-regular-line-height);
43
+ --radio-group-helper-regular-font-family: var(--jh-font-helper-regular-font-family);
44
+ --radio-group-helper-regular-font-weight: var(--jh-font-helper-regular-font-weight);
45
+ --radio-group-helper-regular-font-size: var(--jh-font-helper-regular-font-size);
46
+ --radio-group-helper-regular-line-height: var(--jh-font-helper-regular-line-height);
47
+ font-family: var(--radio-group-helper-regular-font-family);
48
+ font-weight: var(--radio-group-helper-regular-font-weight);
49
+ font-size: var(--radio-group-helper-regular-font-size);
50
+ line-height: var(--radio-group-helper-regular-line-height);
46
51
  display: block;
47
52
  }
48
53
  /* reset styling on fieldset and legend */
@@ -51,6 +56,18 @@ export class JhRadioGroup extends LitElement {
51
56
  padding: 0;
52
57
  margin: 0;
53
58
  }
59
+ :host([disabled]) {
60
+ --group-disabled-opacity: var(--jh-radio-group-opacity-disabled, var(--jh-opacity-disabled));
61
+ }
62
+ :host([disabled]) .label,
63
+ :host([disabled]) .helper-text,
64
+ :host([disabled]) .error-text {
65
+ opacity: var(--group-disabled-opacity);
66
+ pointer-events: none;
67
+ }
68
+ :host([disabled]) ::slotted(jh-radio) {
69
+ --jh-radio-opacity-disabled: var(--group-disabled-opacity);
70
+ }
54
71
  :host legend {
55
72
  padding: 0;
56
73
  }
@@ -85,6 +102,10 @@ export class JhRadioGroup extends LitElement {
85
102
  --jh-radio-group-label-color-text,
86
103
  var(--jh-color-content-primary-enabled)
87
104
  );
105
+ font-family: var(--jh-font-helper-medium-font-family);
106
+ font-weight: var(--jh-font-helper-medium-font-weight);
107
+ font-size: var(--jh-font-helper-medium-font-size);
108
+ line-height: var(--jh-font-helper-medium-line-height);
88
109
  }
89
110
  .helper-text {
90
111
  color: var(
@@ -110,6 +131,10 @@ export class JhRadioGroup extends LitElement {
110
131
  --jh-radio-group-required-color-text-optional,
111
132
  var(--jh-color-content-primary-enabled)
112
133
  );
134
+ font-family: var(--radio-group-helper-regular-font-family);
135
+ font-weight: var(--radio-group-helper-regular-font-weight);
136
+ font-size: var(--radio-group-helper-regular-font-size);
137
+ line-height: var(--radio-group-helper-regular-line-height);
113
138
  }
114
139
  :host([show-indicator][required]) .indicator {
115
140
  color: var(
@@ -126,6 +151,11 @@ export class JhRadioGroup extends LitElement {
126
151
  type: String,
127
152
  attribute: 'accessible-label',
128
153
  },
154
+ /** Disables the radio group and prevents all user interactions. May cause the group to be ignored by assistive technologies (AT). */
155
+ disabled: {
156
+ type: Boolean,
157
+ reflect: true
158
+ },
129
159
  /** Text to be displayed when radio group has failed validation and `invalid` is true. */
130
160
  errorText: {
131
161
  type: String,
@@ -181,17 +211,19 @@ export class JhRadioGroup extends LitElement {
181
211
  this.#internals = this.attachInternals();
182
212
  /** @type {?string} */
183
213
  this.accessibleLabel = null;
214
+ /** @type {boolean} */
215
+ this.disabled = false;
184
216
  /** @type {?string} */
185
217
  this.errorText = null;
186
218
  /** @type {?string} */
187
219
  this.helperText = null;
188
- /** @type {?Boolean} */
220
+ /** @type {?boolean} */
189
221
  this.invalid = false;
190
222
  /** @type {?string} */
191
223
  this.label = null;
192
224
  /** @type {?string} */
193
225
  this.name = null;
194
- /** @type {?Boolean} */
226
+ /** @type {?boolean} */
195
227
  this.required = false;
196
228
  /** @type {'vertical'|'horizontal'} */
197
229
  this.orientation = 'vertical';
@@ -210,6 +242,16 @@ export class JhRadioGroup extends LitElement {
210
242
  this.#id = id++;
211
243
  }
212
244
 
245
+ firstUpdated() {
246
+ this._syncDisabledToChildren();
247
+ }
248
+
249
+ updated(changedProperties) {
250
+ if (changedProperties.has('disabled')) {
251
+ this._syncDisabledToChildren();
252
+ }
253
+ }
254
+
213
255
  /**
214
256
  * Returns the radio group's parent form element.
215
257
  * @type {?HTMLFormElement}
@@ -237,6 +279,16 @@ export class JhRadioGroup extends LitElement {
237
279
  this.requestUpdate('value', oldValue);
238
280
  }
239
281
 
282
+ _syncDisabledToChildren() {
283
+ const slot = this.renderRoot.querySelector('slot');
284
+ if(!slot) return;
285
+
286
+ const radios = slot.assignedElements().filter((el) => el.tagName === 'JH-RADIO');
287
+ radios.forEach((radio) => {
288
+ radio.disabled = this.disabled;
289
+ });
290
+ }
291
+
240
292
  #getRadios() {
241
293
  return [...this.querySelectorAll('jh-radio')];
242
294
  }
@@ -260,6 +312,8 @@ export class JhRadioGroup extends LitElement {
260
312
  if (!this.#checked) {
261
313
  radios[0].tabIndex = 0;
262
314
  }
315
+
316
+ this._syncDisabledToChildren();
263
317
  }
264
318
 
265
319
  #handleChange(e) {
@@ -384,6 +438,7 @@ export class JhRadioGroup extends LitElement {
384
438
  role="radiogroup"
385
439
  id=${ifDefined(this.label ? `radio-group-label-${this.#id}` : null)}
386
440
  aria-describedby=${ifDefined(this.#getAriaDescribedBy())}
441
+ aria-disabled=${ifDefined(this.disabled ? 'true' : null)}
387
442
  aria-required=${ifDefined(this.required ? 'true' : 'false')}
388
443
  aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
389
444
  aria-label=${ifDefined(this.accessibleLabel)}