Skip to content

Commit

Permalink
feat(a11y): add aria invalid and describedby attributes on validation
Browse files Browse the repository at this point in the history
Closes #48
  • Loading branch information
anehx committed Nov 9, 2022
1 parent d46434c commit 6e16a51
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 6 deletions.
16 changes: 12 additions & 4 deletions addon/components/validated-input.hbs
Expand Up @@ -21,11 +21,11 @@
}}

{{#if @hint}}
<this.hintComponent @hint={{@hint}} />
<this.hintComponent @hint={{@hint}} id={{this.hintId}} />
{{/if}}

{{#if (and this.showValidity this.errors)}}
<this.errorComponent @errors={{this.errors}} />
<this.errorComponent @errors={{this.errors}} id={{this.errorId}} />
{{/if}}
{{else}}
<this.renderComponent
Expand Down Expand Up @@ -64,12 +64,20 @@
}}
@hintComponent={{if
@hint
(component (ensure-safe-component this.hintComponent) hint=@hint)
(component
(ensure-safe-component this.hintComponent) hint=@hint id=this.hintId
)
}}
@errorComponent={{if
(and this.showValidity this.errors)
(component (ensure-safe-component this.errorComponent) errors=this.errors)
(component
(ensure-safe-component this.errorComponent)
errors=this.errors
id=this.errorId
)
}}
aria-invalid={{if this.isInvalid "true"}}
aria-describedby={{if this.isInvalid this.errorId this.hintId}}
...attributes
/>
{{/if}}
9 changes: 9 additions & 0 deletions addon/components/validated-input.js
@@ -1,5 +1,6 @@
import { action, set, get } from "@ember/object";
import { guidFor } from "@ember/object/internals";
import { isEmpty } from "@ember/utils";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

Expand All @@ -21,6 +22,14 @@ import passedOrDefault from "ember-validated-form/passed-or-default";
export default class ValidatedInputComponent extends Component {
inputId = guidFor(this);

get errorId() {
return `${this.inputId}-error`;
}

get hintId() {
return !isEmpty(this.args.hint) ? `${this.inputId}-hint` : null;
}

@tracked dirty;
@tracked required;
@tracked type;
Expand Down
3 changes: 2 additions & 1 deletion addon/components/validated-input/error.hbs
@@ -1,9 +1,10 @@
{{#if (macroCondition (macroGetOwnConfig "isUikit"))}}
<small class="uk-text-danger" ...attributes>
<small id={{@id}} class="uk-text-danger" ...attributes>
{{yield}}{{this.errorString}}
</small>
{{else}}
<span
id={{@id}}
class={{if
(macroCondition (macroGetOwnConfig "isBootstrap"))
"d-block invalid-feedback"
Expand Down
1 change: 1 addition & 0 deletions addon/components/validated-input/hint.hbs
@@ -1,4 +1,5 @@
<small
id={{@id}}
class={{class-list
(if (macroCondition (macroGetOwnConfig "isUikit")) "uk-text-muted")
(if
Expand Down
2 changes: 2 additions & 0 deletions addon/components/validated-input/types/checkbox-group.hbs
Expand Up @@ -14,6 +14,7 @@
id="{{@inputId}}-{{i}}"
disabled={{@disabled}}
{{on "input" (fn this.onUpdate option.key)}}
...attributes
/>
{{option.label}}
</label>
Expand All @@ -29,6 +30,7 @@
id="{{@inputId}}-{{i}}"
disabled={{@disabled}}
{{on "input" (fn this.onUpdate option.key)}}
...attributes
/>
<label
class="custom-control-label"
Expand Down
2 changes: 2 additions & 0 deletions addon/components/validated-input/types/checkbox.hbs
Expand Up @@ -10,6 +10,7 @@
disabled={{@disabled}}
checked={{@value}}
{{on "input" this.onUpdate}}
...attributes
/>
</@labelComponent>
{{else if (macroCondition (macroGetOwnConfig "isBootstrap"))}}
Expand All @@ -24,6 +25,7 @@
disabled={{@disabled}}
checked={{@value}}
{{on "input" this.onUpdate}}
...attributes
/>
<@labelComponent class="custom-control-label" />
</div>
Expand Down
2 changes: 2 additions & 0 deletions addon/components/validated-input/types/radio-group.hbs
Expand Up @@ -15,6 +15,7 @@
id="{{@inputId}}-{{i}}"
disabled={{@disabled}}
{{on "input" (fn this.onUpdate option.key)}}
...attributes
/>
{{option.label}}
</label>
Expand All @@ -31,6 +32,7 @@
id="{{@inputId}}-{{i}}"
disabled={{@disabled}}
{{on "input" (fn this.onUpdate option.key)}}
...attributes
/>
<label
class="custom-control-label"
Expand Down
2 changes: 1 addition & 1 deletion tests/dummy/app/components/permanent-custom-hint.hbs
@@ -1,5 +1,5 @@
{{! BEGIN-SNIPPET permanent-custom-hint-component-template.hbs }}
<small class="form-text text-muted">
<small id={{@id}} class="form-text text-muted">
<FaIcon @icon="question-circle" />
{{@hint}}
</small>
Expand Down
10 changes: 10 additions & 0 deletions tests/integration/components/validated-form-test.js
Expand Up @@ -87,6 +87,10 @@ module("Integration | Component | validated form", function (hooks) {
assert.dom("input + div").doesNotExist();
assert.dom("input + small").exists({ count: 1 });
assert.dom("input + small").hasText("Not your middle name!");

const hintId = this.element.querySelector("input + small").id;

assert.dom("input").hasAria("describedby", hintId);
});

testDefault(
Expand Down Expand Up @@ -142,6 +146,7 @@ module("Integration | Component | validated form", function (hooks) {
`);

assert.dom("span.invalid-feedback").doesNotExist();
assert.dom("input").doesNotHaveAria("invalid");

await click("button");

Expand All @@ -150,6 +155,11 @@ module("Integration | Component | validated form", function (hooks) {
assert
.dom("span.invalid-feedback")
.hasText("First name must be between 3 and 40 characters");

const errorId = this.element.querySelector("span.invalid-feedback").id;

assert.dom("input").hasAria("invalid", "true");
assert.dom("input").hasAria("describedby", errorId);
}
);

Expand Down

0 comments on commit 6e16a51

Please sign in to comment.