From 771e0eb68a48f7c6669ece9ddcb4576e0736b45a Mon Sep 17 00:00:00 2001 From: tshimber Date: Wed, 16 Oct 2024 21:33:37 +0200 Subject: [PATCH] feat: aria-details added to diff input types --- .../src/components/checkbox/checkbox.tsx | 5 ++++- .../components/date-picker/date-picker.tsx | 20 +++++++++++++------ .../dropdown-select/dropdown-select.tsx | 5 ++++- .../src/components/dropdown/dropdown.tsx | 10 ++++++++-- .../components/radio-button/radio-button.tsx | 10 ++++++++-- .../src/components/textarea/textarea.tsx | 10 ++++++++-- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/packages/components/src/components/checkbox/checkbox.tsx b/packages/components/src/components/checkbox/checkbox.tsx index 65b9a44936..a341a77324 100644 --- a/packages/components/src/components/checkbox/checkbox.tsx +++ b/packages/components/src/components/checkbox/checkbox.tsx @@ -65,6 +65,8 @@ export class Checkbox { @Prop() styles?: string; /** (optional) Input required */ @Prop() required?: boolean; + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; /** Emitted when the value has changed. */ @Event({ eventName: 'scale-change' }) scaleChange: EventEmitter; @@ -189,7 +191,8 @@ export class Checkbox { aria-label={this.ariaLabelCheckbox} aria-checked={this.indeterminate ? 'mixed' : false} aria-invalid={this.status === 'error' || this.invalid ? 'true' : null} - aria-describedBy={helperText.id} + aria-describedBy={helperText ? helperText.id : this.ariaDetailsId} + aria-details={this.ariaDetailsId} disabled={this.disabled} required={this.required} onChange={this.handleChange} diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index 5a15423b68..a202a02a30 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -151,6 +151,10 @@ export class DatePicker { @Prop() variant?: 'informational' | 'warning' | 'danger' | 'success' = 'informational'; + + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; + /** Whether the input element has focus */ @State() hasFocus: boolean = false; @@ -308,11 +312,15 @@ export class DatePicker { input.addEventListener('keyup', this.handleKeyPress); } - if (input && this.helperText) { - input.setAttribute( - 'aria-describedby', - `helper-message-${this.internalId}` - ); + if (input && (this.helperText || this.ariaDetailsId)) { + const describedBy = this.helperText + ? `helper-message-${this.internalId}` + : this.ariaDetailsId; + input.setAttribute('aria-describedby', describedBy); + } + + if (input && this.ariaDetailsId) { + input.setAttribute('aria-details', this.ariaDetailsId); } if (input && this.placeholder) { @@ -483,7 +491,7 @@ export class DatePicker { dateAdapter={this.dateAdapter} disabled={this.disabled} value={this.value} - ref={(element: HTMLElement & DuetDatePicker) => + ref={(element: HTMLDuetDatePickerElement & DuetDatePicker) => (this.duetInput = element) } > diff --git a/packages/components/src/components/dropdown-select/dropdown-select.tsx b/packages/components/src/components/dropdown-select/dropdown-select.tsx index afe80c06ce..b487b3aee6 100644 --- a/packages/components/src/components/dropdown-select/dropdown-select.tsx +++ b/packages/components/src/components/dropdown-select/dropdown-select.tsx @@ -257,6 +257,8 @@ export class DropdownSelect { @Prop() ariaLabelSelected?: string = 'selected'; /** (optional) Text displayed in high contrast mode only to indicate disabled state */ @Prop() hcmLabelDisabled?: string = 'this field is disabled'; + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; @Event({ eventName: 'scale-change' }) scaleChange!: EventEmitter; @Event({ eventName: 'scale-focus' }) scaleFocus!: EventEmitter; @@ -490,7 +492,8 @@ export class DropdownSelect { const ValueElement = element.ItemElement; const hasEmptyValueElement = element.value === ''; const helperTextId = `helper-message-${generateUniqueId()}`; - const ariaDescribedByAttr = { 'aria-describedBy': helperTextId }; + const describedBy = this.helperText ? helperTextId : this.ariaDetailsId; + const ariaDescribedByAttr = { 'aria-describedBy': describedBy }; return ( diff --git a/packages/components/src/components/dropdown/dropdown.tsx b/packages/components/src/components/dropdown/dropdown.tsx index ffd36e0bba..2adfa3745c 100644 --- a/packages/components/src/components/dropdown/dropdown.tsx +++ b/packages/components/src/components/dropdown/dropdown.tsx @@ -70,6 +70,8 @@ export class Dropdown { @Prop() controlled?: boolean = false; /** (optional) to avoid displaying the label */ @Prop() hideLabelVisually?: boolean = false; + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; /** (optional) Injected CSS styles */ @Prop() styles?: string; @@ -242,7 +244,8 @@ export class Dropdown { const ariaInvalidAttr = this.status === 'error' || this.invalid ? { 'aria-invalid': 'true' } : {}; const helperTextId = `helper-message-${this.internalId}`; - const ariaDescribedByAttr = { 'aria-describedBy': helperTextId }; + const describedBy = this.helperText ? helperTextId : this.ariaDetailsId; + const ariaDescribedByAttr = { 'aria-describedBy': describedBy }; return ( @@ -268,7 +271,10 @@ export class Dropdown { name={this.name} size={this.visibleSize} {...ariaInvalidAttr} - {...(this.helperText ? ariaDescribedByAttr : {})} + {...(this.helperText || this.ariaDetailsId + ? ariaDescribedByAttr + : {})} + aria-details={this.ariaDetailsId} > diff --git a/packages/components/src/components/radio-button/radio-button.tsx b/packages/components/src/components/radio-button/radio-button.tsx index 42e84849b1..5ba1d3e7c9 100644 --- a/packages/components/src/components/radio-button/radio-button.tsx +++ b/packages/components/src/components/radio-button/radio-button.tsx @@ -53,6 +53,8 @@ export class RadioButton { @Prop() inputId?: string; /** (optional) Injected CSS styles */ @Prop() styles?: string; + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; @Event({ eventName: 'scale-change' }) scaleChange!: EventEmitter; @@ -124,7 +126,8 @@ export class RadioButton { const ariaInvalidAttr = this.status === 'error' || this.invalid ? { 'aria-invalid': 'true' } : {}; const helperTextId = `helper-message-${this.internalId}`; - const ariaDescribedByAttr = { 'aria-describedBy': helperTextId }; + const describedBy = this.helperText ? helperTextId : this.ariaDetailsId; + const ariaDescribedByAttr = { 'aria-describedBy': describedBy }; return ( @@ -138,7 +141,10 @@ export class RadioButton { checked={this.checked} disabled={this.disabled} {...ariaInvalidAttr} - {...(this.helperText ? ariaDescribedByAttr : {})} + {...(this.helperText || this.ariaDetailsId + ? ariaDescribedByAttr + : {})} + aria-details={this.ariaDetailsId} /> {!!this.helperText && ( diff --git a/packages/components/src/components/textarea/textarea.tsx b/packages/components/src/components/textarea/textarea.tsx index 3405e05c51..b8d39bc831 100644 --- a/packages/components/src/components/textarea/textarea.tsx +++ b/packages/components/src/components/textarea/textarea.tsx @@ -77,6 +77,8 @@ export class Textarea { @Prop() inputAutofocus?: boolean; /** (optional) Injected CSS styles */ @Prop() styles?: string; + /** (optional) id or space separated list of ids of elements that provide or link to additional related information. */ + @Prop() ariaDetailsId?: string; /** Emitted when a keyboard input occurred. */ @Event({ eventName: 'scale-input' }) scaleInput!: EventEmitter; @@ -173,7 +175,8 @@ export class Textarea { const ariaInvalidAttr = this.status === 'error' || this.invalid ? { 'aria-invalid': 'true' } : {}; const helperTextId = `helper-message-${this.internalId}`; - const ariaDescribedByAttr = { 'aria-describedBy': helperTextId }; + const describedBy = this.helperText ? helperTextId : this.ariaDetailsId; + const ariaDescribedByAttr = { 'aria-describedBy': describedBy }; const readonlyAttr = this.readonly ? { readonly: 'readonly' } : {}; return ( @@ -212,7 +215,10 @@ export class Textarea { {...(!!this.rows ? { rows: this.rows } : {})} {...(!!this.cols ? { cols: this.cols } : {})} {...ariaInvalidAttr} - {...(this.helperText ? ariaDescribedByAttr : {})} + {...(this.helperText || this.ariaDetailsId + ? ariaDescribedByAttr + : {})} + aria-details={this.ariaDetailsId} ref={(el) => (this.focusableElement = el)} />