Skip to content
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
27 changes: 27 additions & 0 deletions packages/common/src/core/__tests__/slickGrid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3101,6 +3101,33 @@ describe('SlickGrid core file', () => {
expect(grid.getContainerNode().getAttribute('aria-rowcount')).toBe('11');
});

it('should expect the header structure to have the ARIA roles required between grid and columnheader', () => {
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, showHeaderRow: true });

const headerColumnElm = container.querySelector('[role="columnheader"]') as HTMLDivElement;
expect(headerColumnElm).toBeTruthy();
// a columnheader requires a "row" parent, and that row requires a "rowgroup" (or grid) parent,
// otherwise axe reports `aria-required-parent` + `aria-required-children` on every grid
expect(headerColumnElm.parentElement!.classList.contains('slick-header-columns')).toBe(true);
expect(headerColumnElm.parentElement!.getAttribute('role')).toBe('row');
expect(headerColumnElm.parentElement!.parentElement!.getAttribute('role')).toBe('rowgroup');

// same skeleton for the header row (filter row): rowgroup > row > gridcell
const headerRowCellElm = container.querySelector('.slick-headerrow-column') as HTMLDivElement;
expect(headerRowCellElm.getAttribute('role')).toBe('gridcell');
expect(headerRowCellElm.parentElement!.getAttribute('role')).toBe('row');
expect(headerRowCellElm.parentElement!.parentElement!.getAttribute('role')).toBe('rowgroup');
});

it('should expect the focus sinks to be hidden from the a11y tree', () => {
grid = new SlickGrid<any, Column>(container, items, columns, defaultOptions);

// 0x0 helper divs with tabindex=-1 are otherwise computed as unallowed children of role="grid"
const sinks = container.querySelectorAll('div[tabindex="-1"]:not([class])');
expect(sinks.length).toBe(2);
sinks.forEach((sink) => expect(sink.getAttribute('aria-hidden')).toBe('true'));
});

it('should return undefined editor when getDataItem() did not find any associated cell item', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
Expand Down
26 changes: 17 additions & 9 deletions packages/common/src/core/slickGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
this._container
);

// 0x0 helper div, programmatically focusable only (tabindex=-1). Exposing it to the a11y tree makes it
// an unallowed child of role="grid" (axe `aria-required-children`), so keep it out of the tree.
this._focusSink.setAttribute('aria-hidden', 'true');

if (this._options.createTopHeaderPanel) {
this._topHeaderPanelScroller = createDomElement(
'div',
Expand Down Expand Up @@ -761,8 +765,10 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
// Append the header scroller containers
const headerContainerL = createDomElement('div', { className: 'slick-header-container' }, this._paneHeaderL);
const headerContainerR = createDomElement('div', { className: 'slick-header-container' }, this._paneHeaderR);
this._headerScrollerL = createDomElement('div', { className: 'slick-header slick-state-default slick-header-left' }, headerContainerL);
this._headerScrollerR = createDomElement('div', { className: 'slick-header slick-state-default slick-header-right' }, headerContainerR);
// prettier-ignore
this._headerScrollerL = createDomElement('div', { className: 'slick-header slick-state-default slick-header-left', role: 'rowgroup' }, headerContainerL);
// prettier-ignore
this._headerScrollerR = createDomElement('div', { className: 'slick-header slick-state-default slick-header-right', role: 'rowgroup' }, headerContainerR);

// header scroll position could change when using frozen grid and tabbing on next available header
// so we need to make sure that all containers (header, headerrow, toppanel) are all in sync when that happens
Expand All @@ -777,20 +783,22 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
// Append the columnn containers to the headers
this._headerL = createDomElement(
'div',
{ className: 'slick-header-columns slick-header-columns-left', style: { left: '-1000px' } },
{ className: 'slick-header-columns slick-header-columns-left', style: { left: '-1000px' }, role: 'row' },
this._headerScrollerL
);
this._headerR = createDomElement(
'div',
{ className: 'slick-header-columns slick-header-columns-right', style: { left: '-1000px' } },
{ className: 'slick-header-columns slick-header-columns-right', style: { left: '-1000px' }, role: 'row' },
this._headerScrollerR
);

// Cache the header columns
this._headers = [this._headerL, this._headerR];

this._headerRowScrollerL = createDomElement('div', { className: 'slick-headerrow slick-state-default' }, this._paneTopL);
this._headerRowScrollerR = createDomElement('div', { className: 'slick-headerrow slick-state-default' }, this._paneTopR);
// prettier-ignore
this._headerRowScrollerL = createDomElement('div', { className: 'slick-headerrow slick-state-default', role: 'rowgroup' }, this._paneTopL);
// prettier-ignore
this._headerRowScrollerR = createDomElement('div', { className: 'slick-headerrow slick-state-default', role: 'rowgroup' }, this._paneTopR);

this._headerRowScroller = [this._headerRowScrollerL, this._headerRowScrollerR];

Expand All @@ -807,12 +815,12 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e

this._headerRowL = createDomElement(
'div',
{ className: 'slick-headerrow-columns slick-headerrow-columns-left' },
{ className: 'slick-headerrow-columns slick-headerrow-columns-left', role: 'row' },
this._headerRowScrollerL
);
this._headerRowR = createDomElement(
'div',
{ className: 'slick-headerrow-columns slick-headerrow-columns-right' },
{ className: 'slick-headerrow-columns slick-headerrow-columns-right', role: 'row' },
this._headerRowScrollerR
);

Expand Down Expand Up @@ -1957,7 +1965,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
if (this._options.showHeaderRow) {
const headerRowCell = createDomElement(
'div',
{ className: `slick-state-default slick-headerrow-column l${i} r${i}` },
{ className: `slick-state-default slick-headerrow-column l${i} r${i}`, role: 'gridcell' },
headerRowTarget
);
const frozenClasses = this.hasFrozenColumns() && i <= this._options.frozenColumn! ? 'frozen' : null;
Expand Down
Loading