Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aria-labelledby, aria-describedby and required properties to the search input #254

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions addon/components/power-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export default Ember.Component.extend({
return !this.get('loading') && this.get('results.length') === 0;
}),

listboxId: computed(function() {
return `${this.get('elementId')}-listbox`;
}),

selectedOptionId: computed(function() {
return `${this.get('elementId')}-selected-option`;
}),

results: computed('options.[]', {
get() {
let options = this.get('options') || [];
Expand Down
13 changes: 13 additions & 0 deletions addon/helpers/ember-power-select-is-selected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Ember from 'ember';

const { isArray } = Ember;

export function emberPowerSelectIsSelected([option, selected]) {
if (isArray(selected)) {
return selected.indexOf(option) > -1;
} else {
return option === selected;
}
}

export default Ember.Helper.helper(emberPowerSelectIsSelected);
38 changes: 29 additions & 9 deletions addon/templates/components/power-select.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
{{#basic-dropdown class=(readonly concatenatedClasses) dir=(readonly dir) tabindex=(readonly tabindex) renderInPlace=(readonly renderInPlace) matchTriggerWidth=true
disabled=(readonly disabled) verticalPosition=(readonly verticalPosition) triggerClass=(readonly concatenatedTriggerClasses) dropdownClass=(readonly concatenatedDropdownClasses)
opened=opened onOpen=(action "handleOpen") onClose=(action "handleClose") onFocus=(action "handleFocus") onKeydown=(action "handleKeydown") registerActionsInParent=(action "registerDropdown") as |dropdown|}}
{{#basic-dropdown
class=(readonly concatenatedClasses)
dir=(readonly dir)
tabindex=(readonly tabindex)
role="combobox"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The combobox role at the moment is added to the input where you type to search. I don't know where the correct place is, but having that in both is probably wrong.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be necessary on both, as the trigger and the real combobox are separate pieces of DOM.

renderInPlace=(readonly renderInPlace)
matchTriggerWidth=true
disabled=(readonly disabled)
verticalPosition=(readonly verticalPosition)
triggerClass=(readonly concatenatedTriggerClasses)
dropdownClass=(readonly concatenatedDropdownClasses)
opened=opened
onOpen=(action "handleOpen")
onClose=(action "handleClose")
onFocus=(action "handleFocus")
onKeydown=(action "handleKeydown")
registerActionsInParent=(action "registerDropdown")
ariaLabelledBy=ariaLabelledBy
ariaDescribedBy=ariaDescribedBy
ariaRequired=ariaRequired
ariaInvalid=ariaInvalid
as |dropdown|}}
{{#with (hash
isOpen=dropdown.isOpen
actions=(hash
Expand All @@ -12,17 +31,17 @@
search=(action "search" dropdown)
handleKeydown=(action "handleKeydown" dropdown)
)) as |select|}}
{{component beforeOptionsComponent onkeydown=(readonly onkeydown) select=(readonly select) searchPlaceholder=(readonly searchPlaceholder) searchEnabled=(readonly searchEnabled) highlighted=(readonly highlighted)}}
{{component beforeOptionsComponent listboxId=listboxId selectedOptionId=selectedOptionId onkeydown=(readonly onkeydown) select=(readonly select) searchPlaceholder=(readonly searchPlaceholder) searchEnabled=(readonly searchEnabled) highlighted=(readonly highlighted)}}
{{#if mustShowSearchMessage}}
<ul class="ember-power-select-options" role="listbox"><li class="ember-power-select-option" role="option">{{searchMessage}}</li></ul>
<ul id={{listboxId}} class="ember-power-select-options" role="listbox"><li class="ember-power-select-option" role="option">{{searchMessage}}</li></ul>
{{else if mustShowNoMessages}}
{{#if (hasBlock "inverse")}}
{{yield to="inverse"}}
{{else if noMatchesMessage}}
<ul class="ember-power-select-options" role="listbox"><li class="ember-power-select-option" role="option">{{noMatchesMessage}}</li></ul>
<ul id={{listboxId}} class="ember-power-select-options" role="listbox"><li class="ember-power-select-option" role="option">{{noMatchesMessage}}</li></ul>
{{/if}}
{{else}}
{{#component optionsComponent options=(readonly results) loading=(readonly loading) allOptions=(readonly results) highlighted=(readonly highlighted)
{{#component optionsComponent id=listboxId selectedOptionId=selectedOptionId options=(readonly results) loading=(readonly loading) allOptions=(readonly results) highlighted=(readonly highlighted)
selected=(readonly selected) optionsComponent=(readonly optionsComponent) searchText=(readonly searchText)
select=(readonly select) extra=(readonly extra) loadingMessage=(readonly loadingMessage)
class="ember-power-select-options" as |option term|}}
Expand All @@ -46,8 +65,9 @@
{{#component triggerComponent options=(readonly results) selected=(readonly selected) searchText=(readonly searchText)
placeholder=(readonly placeholder) disabled=(readonly disabled) highlighted=(readonly highlighted) allowClear=(readonly allowClear)
select=(readonly select) extra=(readonly extra) onkeydown=(readonly onkeydown)
selectedItemComponent=(readonly selectedItemComponent) searchField=(readonly searchField) as |opt term|}}
selectedItemComponent=(readonly selectedItemComponent)
searchField=(readonly searchField) as |opt term|}}
{{yield opt term}}
{{/component}}
{{/with}}
{{/basic-dropdown}}
{{/basic-dropdown}}
18 changes: 14 additions & 4 deletions addon/templates/components/power-select/before-options.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
{{#if searchEnabled}}
<div class="ember-power-select-search">
<input type="search" autocomplete="off" autocorrect="off" autocapitalize="off"
spellcheck="false" role="combobox" oninput={{action select.actions.search value="target.value"}}
onkeydown={{action "handleKeydown"}} placeholder={{searchPlaceholder}}>
<input type="search"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
role="combobox"
aria-expanded="true"
aria-autocomplete="list"
aria-owns={{listboxId}}
aria-activedescendant={{selectedOptionId}}
oninput={{action select.actions.search value="target.value"}}
onkeydown={{action "handleKeydown"}}
placeholder={{searchPlaceholder}}>
</div>
{{/if}}
{{/if}}
4 changes: 3 additions & 1 deletion addon/templates/components/power-select/options.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{{/if}}
{{#each options as |opt|}}
{{#if opt.groupName}}
<li class="ember-power-select-group" role="option">
<li class="ember-power-select-group" role="option" >
<span class="ember-power-select-group-name">{{opt.groupName}}</span>
{{#component optionsComponent highlighted=(readonly highlighted) selected=(readonly selected)
options=(readonly opt.options) allOptions=(readonly allOptions) optionsComponent=(readonly optionsComponent) select=(readonly select)
Expand All @@ -15,6 +15,8 @@
</li>
{{else}}
<li class="ember-power-select-option {{ember-power-select-option-classes opt selected highlighted}}"
id={{if (ember-power-select-is-selected opt selected) selectedOptionId}}
aria-selected={{ember-power-select-is-selected opt selected}}
onmouseup={{action select.actions.choose (ember-power-select-build-selection opt selected multiple=multiple)}}
onmouseover={{action select.actions.highlight opt}} role="option">
{{yield opt searchText}}
Expand Down
1 change: 1 addition & 0 deletions app/helpers/ember-power-select-is-selected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, emberPowerSelectIsSelected } from 'ember-power-select/helpers/ember-power-select-is-selected';
59 changes: 59 additions & 0 deletions tests/integration/components/power-select/accessibility-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
// import { clickTrigger } from '../../../helpers/ember-power-select';

moduleForComponent('power-select', 'Integration | Component | Ember Power Select (Accessibility)', {
integration: true
});

test('The aria-labelledby property can be set', function(assert) {
assert.expect(1);
this.render(hbs`
<label id="foo123">Foo</label>
{{#power-select selected=selected options=options ariaRequired="true" ariaLabelledBy="foo123" onchange=(action (mut foo)) as |option|}}
{{option}}
{{/power-select}}
`);
assert.equal($('.ember-power-select-trigger').attr('aria-labelledby'), 'foo123');
});

test('The aria-describedby property can be set', function(assert) {
assert.expect(1);
this.render(hbs`
{{#power-select ariaDescribedBy="foo123" onchange=(action (mut foo)) as |option|}}
{{option}}
{{/power-select}}
`);
assert.equal($('.ember-power-select-trigger').attr('aria-describedBy'), 'foo123');
});

test('The aria-required property can be set', function(assert) {
assert.expect(1);
this.render(hbs`
{{#power-select ariaRequired=true onchange=(action (mut foo)) as |option|}}
{{option}}
{{/power-select}}
`);
assert.equal($('.ember-power-select-trigger').attr('aria-required'), 'true');
});

test('The aria-invalid property can be set', function(assert) {
assert.expect(1);
this.render(hbs`
{{#power-select ariaInvalid=true onchange=(action (mut foo)) as |option|}}
{{option}}
{{/power-select}}
`);
assert.equal($('.ember-power-select-trigger').attr('aria-invalid'), 'true');
});

test('The search input has the aria-autocomplete="list" attr', function(assert) {
assert.async();
this.options = ['male', 'female', 'unknown'];
this.selected = ['female'];
this.render(hbs`
{{#power-select options=options selected=selected onchange=(action (mut selected)) as |option|}}
{{option}}
{{/power-select}}
`);
});
10 changes: 10 additions & 0 deletions tests/unit/helpers/ember-power-select-is-selected-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { emberPowerSelectIsSelected } from '../../../helpers/ember-power-select-is-selected';
import { module, test } from 'qunit';

module('Unit | Helper | ember power select is selected');

// Replace this with your real tests.
test('it works', function(assert) {
let result = emberPowerSelectIsSelected([42]);
assert.ok(result);
});