Skip to content

Commit

Permalink
fix(adobe#249): [Masonry][Accessibility] Files: Main Navigation (Card…
Browse files Browse the repository at this point in the history
… View) - Selected state of the folder is not announced to the screen reader
  • Loading branch information
majornista committed Oct 11, 2022
1 parent 305d3f6 commit 11f064a
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 26 deletions.
7 changes: 2 additions & 5 deletions coral-component-masonry/examples/index.html
Expand Up @@ -145,7 +145,7 @@ <h2 class="coral-Heading--M">Usage notes</h2>
<article>
<span coral-masonry-draghandle></span>
With image
<img src="http://via.placeholder.com/600x300" alt="600 × 300" />
<img src="https://via.placeholder.com/600x300" alt="600 × 300" />
</article>
</coral-masonry-item>
<coral-masonry-item class="coral-Well">
Expand Down Expand Up @@ -234,7 +234,6 @@ <h2 class="coral-Heading--M">Usage notes</h2>
item.id = getUID();
}
article.id = article.id || item.id + '-content';
item.setAttribute('aria-labelledby', article.id);
});

const ariaGridSelector = document.getElementById('ariaGrid');
Expand Down Expand Up @@ -301,11 +300,10 @@ <h2 class="coral-Heading--M">Usage notes</h2>
const contentId = id + '-content';
const width = randomSize();
const height = randomSize();
const url = 'http://via.placeholder.com/' + width + 'x' + height;
const url = 'https://via.placeholder.com/' + width + 'x' + height;

const item = document.createElement('coral-masonry-item');
item.id = id;
item.setAttribute('aria-labelledby', contentId);
item.classList.add('coral-Well');
item.content.innerHTML = '<img id="' + contentId + '" alt="' + width + ' × ' + height + '" src="' + url + '" style="width: ' + (width / 2) + 'px; height: ' + (height / 2) + 'px;">';

Expand All @@ -320,7 +318,6 @@ <h2 class="coral-Heading--M">Usage notes</h2>

const item = document.createElement('coral-masonry-item');
item.id = id;
item.setAttribute('aria-labelledby', contentId);
item.classList.add('coral-Well');
item.content.innerHTML = '<article role="presentation" id="' + contentId + '">' + html + '</article>';
return item;
Expand Down
15 changes: 12 additions & 3 deletions coral-component-masonry/src/scripts/Masonry.js
Expand Up @@ -301,7 +301,7 @@ const Masonry = Decorator(class extends BaseComponent(HTMLElement) {

// @a11y only persist the checked state on macOS,
// where VoiceOver does not announce the selected state for a gridcell.
accessibilityState.hidden = true;
accessibilityState.hidden = !isMacLike || !self.selected;
if (!isMacLike || !self.selected) {
accessibilityState.innerHTML = '';
}
Expand Down Expand Up @@ -598,9 +598,15 @@ const Masonry = Decorator(class extends BaseComponent(HTMLElement) {
if (activateAriaGrid === ariaGrid.ON) {
item.setAttribute('role', 'gridcell');
item.setAttribute('aria-colindex', columnIndex);

// communicate aria-selected state of all cells
if (this.selectionMode !== selectionMode.NONE || this.parentElement.hasAttribute('aria-multiselectable')) {
item.setAttribute('aria-selected', item.selected);
}
} else {
item.removeAttribute('role');
item.removeAttribute('aria-colindex');
item.removeAttribute('aria-selected');
}
}

Expand All @@ -621,10 +627,13 @@ const Masonry = Decorator(class extends BaseComponent(HTMLElement) {
const selectedItems = this.selectedItems;

if (this.selectionMode === selectionMode.NONE) {
selectedItems.forEach((selectedItem) => {
this.items.getAll().forEach((item) => {
// Don't trigger change events
this._preventTriggeringEvents = true;
selectedItem.removeAttribute('selected');
if (item.selected) {
item.removeAttribute('selected');
}
item.removeAttribute('aria-selected');
});
} else if (this.selectionMode === selectionMode.SINGLE) {
// Last selected item wins if multiple selection while not allowed
Expand Down
17 changes: 9 additions & 8 deletions coral-component-masonry/src/scripts/MasonryItem.js
Expand Up @@ -246,18 +246,18 @@ const MasonryItem = Decorator(class extends BaseComponent(HTMLElement) {
if (!accessibilityState.parentNode) {
this.appendChild(accessibilityState);
}

// @a11y Item should be labelled by accessibility state.
if (isMacLike) {
const ariaLabelledby = this.getAttribute('aria-labelledby');
if (ariaLabelledby) {
this.setAttribute('aria-labelledby', ariaLabelledby + ' ' + accessibilityState.id);
}
}
});

this._elements.accessibilityState = accessibilityState;

// @a11y Item should be labelled by accessibility state.
if (isMacLike) {
const ariaLabelledby = this.getAttribute('aria-labelledby');
if (ariaLabelledby) {
this.setAttribute('aria-labelledby', ariaLabelledby + ' ' + accessibilityState.id);
}
}

// Support cloneNode
const template = this.querySelector('._coral-Masonry-item-quickActions');
if (template) {
Expand All @@ -266,6 +266,7 @@ const MasonryItem = Decorator(class extends BaseComponent(HTMLElement) {
this.insertBefore(this._elements.quickactions, this.firstChild);
// todo workaround to not give user possibility to tab into checkbox
this._elements.check._labellableElement.tabIndex = -1;
this._elements.check.setAttribute('aria-hidden', 'true');
}

/** @ignore */
Expand Down
44 changes: 34 additions & 10 deletions coral-component-masonry/src/tests/test.Masonry.js
Expand Up @@ -513,16 +513,16 @@ describe('Masonry', function () {
item.selected = true;

setTimeout(function() {
expect(a11yState.textContent).to.equal(i18n.get('checked'));
expect(a11yState.textContent).to.equal(i18n.get('checked'), 'after ~275ms accessibilityState should read "checked"');
expect(a11yState.hidden).to.be.false;
expect(a11yState.hasAttribute('aria-live')).to.be.false;
setTimeout(function() {
expect(a11yState.textContent).to.equal(isMacLike ? i18n.get('checked') : '');
expect(a11yState.hidden).to.be.true;
expect(a11yState.textContent).to.equal(isMacLike ? i18n.get('checked') : '', 'after 1600ms accessibilityState should read "" or "checked" on macOS');
expect(a11yState.hidden).to.equal(!isMacLike, 'on macOS, the "checked" accessibilityState should not be hidden');
expect(a11yState.getAttribute('aria-live')).equal('off');
done();
}, 1650);
}, 220);
}, 1600);
}, 275);
});
});

Expand All @@ -541,16 +541,16 @@ describe('Masonry', function () {
item.selected = true;
item.selected = false;
setTimeout(function() {
expect(a11yState.textContent).to.equal(i18n.get('not checked'));
expect(a11yState.textContent).to.equal(i18n.get('not checked'), 'after ~275ms accessibilityState should read "not checked"');
expect(a11yState.hidden).to.be.false;
expect(a11yState.hasAttribute('aria-live')).to.be.false;
setTimeout(function() {
expect(a11yState.textContent).to.equal('');
expect(a11yState.textContent).to.equal('', 'after 1600ms accessibilityState should read ""');
expect(a11yState.hidden).to.be.true;
expect(a11yState.getAttribute('aria-live')).to.equal('off');
done();
}, 1650);
}, 210);
}, 1600);
}, 275);
});
});
});
Expand All @@ -571,6 +571,8 @@ describe('Masonry', function () {
.to.equal('gridcell', '<coral-masonry-item> should have role="gridcell"');
expect(el.items.last().getAttribute('aria-colindex'))
.to.equal('3', 'last <coral-masonry-item> should have aria-colindex="3"');
expect(el.items.first().hasAttribute('aria-selected'))
.to.equal(false, '<coral-masonry-item> should not have aria-selected when selectionMode="none"');

// Disable aria grid dynamically
el.ariaGrid = "off";
Expand All @@ -595,9 +597,31 @@ describe('Masonry', function () {
expect(el.parentElement.getAttribute('aria-label')).to.equal('Masonry Label', 'Masonry parent element should receive same aria-label as Masonry');
expect(el.parentElement.getAttribute('aria-labelledby')).to.equal('Masonry Labelledby', 'Masonry parent element should receive same aria-labelledby as Masonry');
});
it('masonry elements should have aria-selected when selectionMode is not "none"', function() {
const el = helpers.build(window.__html__['Masonry.items.selected.html']);

el.ariaGrid = "on";

expect(el.items.first().getAttribute('aria-selected'))
.to.equal('true', 'selected <coral-masonry-item> should have aria-selected="true" when selectionMode="single"');
expect(el.items.last().getAttribute('aria-selected'))
.to.equal('false', 'not selected <coral-masonry-item> should have aria-selected="false" when selectionMode="single"');

el.selectionMode = 'multiple';
expect(el.items.first().getAttribute('aria-selected'))
.to.equal('true', 'selected <coral-masonry-item> should have aria-selected="true" when selectionMode="multiple"');
expect(el.items.last().getAttribute('aria-selected'))
.to.equal('false', 'not selected <coral-masonry-item> should have aria-selected="false" when selectionMode="multiple"');

el.selectionMode = 'none';
expect(el.items.first().hasAttribute('aria-selected'))
.to.equal(false, 'selected <coral-masonry-item> should not have aria-selected when selectionMode="none"');
expect(el.items.last().hasAttribute('aria-selected'))
.to.equal(false, 'not selected <coral-masonry-item> should note have aria-selected="false" when selectionMode="none"');
})
});

describe('Attach/Detch', function () {
describe('Attach/Detach', function () {
it('changing masonry parent should keep child intact', function (done) {
const el = helpers.build(window.__html__['Masonry.with.div.wrapper.html']);
const masonry = el.querySelector("coral-masonry");
Expand Down
5 changes: 5 additions & 0 deletions coral-gulp/configs/karma.conf.js
Expand Up @@ -159,6 +159,11 @@ module.exports = function (config) {
client: {
// Set to true for debugging via e.g console.debug
captureConsole: false
/* ,
// Override the timeout, should tests fail due to timeout errors.
mocha: {
timeout: 2500
} */
}
});
};

0 comments on commit 11f064a

Please sign in to comment.