diff --git a/__mocks__/genInteractives.js b/__mocks__/genInteractives.js index f12ebcb06..a36b12955 100644 --- a/__mocks__/genInteractives.js +++ b/__mocks__/genInteractives.js @@ -22,6 +22,7 @@ const interactiveElementsMap = { button: [], canvas: [], datalist: [], + dialog: [], embed: [], input: [], 'input[type="button"]': [{ prop: 'type', value: 'button' }], @@ -71,7 +72,6 @@ const nonInteractiveElementsMap: {[string]: Array<{[string]: string}>} = { del: [], details: [], dfn: [], - dialog: [], dir: [], dl: [], dt: [], @@ -140,13 +140,13 @@ const interactiveRoles = [] ) .filter((role) => ( !roles.get(role).abstract - && roles.get(role).superClass.some((klasses) => includes(klasses, 'widget')) + && roles.get(role).superClass.some((klasses) => includes(klasses, 'widget') || includes(klasses, 'window')) )); const nonInteractiveRoles = roleNames .filter((role) => ( !roles.get(role).abstract - && !roles.get(role).superClass.some((klasses) => includes(klasses, 'widget')) + && !roles.get(role).superClass.some((klasses) => includes(klasses, 'widget') || includes(klasses, 'window')) // 'toolbar' does not descend from widget, but it does support // aria-activedescendant, thus in practice we treat it as a widget. diff --git a/__tests__/src/rules/control-has-associated-label-test.js b/__tests__/src/rules/control-has-associated-label-test.js index fd2950830..097a509de 100644 --- a/__tests__/src/rules/control-has-associated-label-test.js +++ b/__tests__/src/rules/control-has-associated-label-test.js @@ -49,11 +49,14 @@ const alwaysValid = [ { code: 'Save' }, { code: '' }, { code: 'Save' }, + { code: 'Save' }, // Interactive Roles + { code: '
Save
' }, { code: '
Save
' }, { code: '
Save
' }, { code: '
Save
' }, { code: '
Save
' }, + { code: '
Save
' }, { code: '
Save
' }, { code: '
Save
' }, { code: '
Save
' }, @@ -119,7 +122,6 @@ const alwaysValid = [ { code: '
' }, { code: '
' }, { code: '' }, - { code: '' }, { code: '' }, { code: '
' }, { code: '
' }, @@ -162,7 +164,6 @@ const alwaysValid = [ { code: '
    ' }, // Non-interactive Roles { code: '
    ' }, - { code: '
    ' }, { code: '
    ' }, { code: '
    ' }, { code: '
    ' }, @@ -170,7 +171,6 @@ const alwaysValid = [ { code: '
    ' }, { code: '
    ' }, { code: '
    ' }, - { code: '
    ' }, { code: '
    ' }, { code: '
    ' }, { code: '
    ' }, diff --git a/__tests__/src/rules/no-noninteractive-element-interactions-test.js b/__tests__/src/rules/no-noninteractive-element-interactions-test.js index c74abae18..5c0268358 100644 --- a/__tests__/src/rules/no-noninteractive-element-interactions-test.js +++ b/__tests__/src/rules/no-noninteractive-element-interactions-test.js @@ -186,10 +186,12 @@ const alwaysValid = [ { code: ' {}} />;' }, { code: ' {}} />;' }, /* HTML elements attributed with an interactive role */ + { code: '<div role="alertdialog" onClick={() => {}} />;' }, { code: '<div role="button" onClick={() => {}} />;' }, { code: '<div role="checkbox" onClick={() => {}} />;' }, { code: '<div role="columnheader" onClick={() => {}} />;' }, { code: '<div role="combobox" onClick={() => {}} />;' }, + { code: '<div role="dialog" onClick={() => {}} />;' }, { code: '<div role="grid" onClick={() => {}} />;' }, { code: '<div role="gridcell" onClick={() => {}} />;' }, { code: '<div role="link" onClick={() => {}} />;' }, @@ -342,7 +344,6 @@ const neverValid = [ { code: '<div contentEditable role="article" onKeyDown={() => {}} />;', errors: [expectedError] }, /* HTML elements attributed with a non-interactive role */ { code: '<div role="alert" onClick={() => {}} />;', errors: [expectedError] }, - { code: '<div role="alertdialog" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="application" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="article" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="banner" onClick={() => {}} />;', errors: [expectedError] }, @@ -350,7 +351,6 @@ const neverValid = [ { code: '<div role="complementary" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="contentinfo" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="definition" onClick={() => {}} />;', errors: [expectedError] }, - { code: '<div role="dialog" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="directory" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="document" onClick={() => {}} />;', errors: [expectedError] }, { code: '<div role="feed" onClick={() => {}} />;', errors: [expectedError] }, diff --git a/src/util/isInteractiveElement.js b/src/util/isInteractiveElement.js index 3475d678c..34aff7266 100644 --- a/src/util/isInteractiveElement.js +++ b/src/util/isInteractiveElement.js @@ -27,7 +27,7 @@ const nonInteractiveRoles = new Set(roleKeys // 'toolbar' does not descend from widget, but it does support // aria-activedescendant, thus in practice we treat it as a widget. && name !== 'toolbar' - && !role.superClass.some((classes) => includes(classes, 'widget')) + && !role.superClass.some((classes) => includes(classes, 'widget') || includes(classes, 'window')) ); }).concat( // The `progressbar` is descended from `widget`, but in practice, its @@ -43,7 +43,7 @@ const interactiveRoles = new Set(roleKeys // The `progressbar` is descended from `widget`, but in practice, its // value is always `readonly`, so we treat it as a non-interactive role. && name !== 'progressbar' - && role.superClass.some((classes) => includes(classes, 'widget')) + && role.superClass.some((classes) => includes(classes, 'widget') || includes(classes, 'window')) ); }).concat( // 'toolbar' does not descend from widget, but it does support diff --git a/src/util/isInteractiveRole.js b/src/util/isInteractiveRole.js index 74bfb623b..e160c2888 100644 --- a/src/util/isInteractiveRole.js +++ b/src/util/isInteractiveRole.js @@ -8,7 +8,7 @@ import flatMap from 'array.prototype.flatmap'; const roles = [...rolesMap.keys()]; const interactiveRoles = roles.filter((name) => ( !rolesMap.get(name).abstract - && rolesMap.get(name).superClass.some((klasses) => includes(klasses, 'widget')) + && rolesMap.get(name).superClass.some((klasses) => includes(klasses, 'widget') || includes(klasses, 'window')) )); // 'toolbar' does not descend from widget, but it does support diff --git a/src/util/isNonInteractiveElement.js b/src/util/isNonInteractiveElement.js index 85e1037a9..1fe3a3006 100644 --- a/src/util/isNonInteractiveElement.js +++ b/src/util/isNonInteractiveElement.js @@ -35,7 +35,7 @@ const nonInteractiveRoles = new Set(roleKeys // treats them both as CellRole and since gridcell is interactive, we consider // cell interactive as well. && name !== 'cell' - && !role.superClass.some((classes) => includes(classes, 'widget')) + && !role.superClass.some((classes) => includes(classes, 'widget') || includes(classes, 'window')) ); }).concat( // The `progressbar` is descended from `widget`, but in practice, its @@ -54,7 +54,7 @@ const interactiveRoles = new Set(roleKeys // This role is meant to have no semantic value. // @see https://www.w3.org/TR/wai-aria-1.2/#generic && name !== 'generic' - && role.superClass.some((classes) => includes(classes, 'widget')) + && role.superClass.some((classes) => includes(classes, 'widget') || includes(classes, 'window')) ); }).concat( // 'toolbar' does not descend from widget, but it does support diff --git a/src/util/isNonInteractiveRole.js b/src/util/isNonInteractiveRole.js index 54aff8550..bb521c42d 100644 --- a/src/util/isNonInteractiveRole.js +++ b/src/util/isNonInteractiveRole.js @@ -14,7 +14,7 @@ import flatMap from 'array.prototype.flatmap'; const roles = [...rolesMap.keys()]; const nonInteractiveRoles = roles.filter((name) => ( !rolesMap.get(name).abstract - && !rolesMap.get(name).superClass.some((klasses) => includes(klasses, 'widget')) + && !rolesMap.get(name).superClass.some((klasses) => includes(klasses, 'widget') || includes(klasses, 'window')) )); /**