From c5131bff47eefdee0b92016ffe4afa944cee0e95 Mon Sep 17 00:00:00 2001 From: Evan Rusackas Date: Mon, 13 Jul 2026 11:39:13 -0700 Subject: [PATCH] chore(a11y): enable 9 more jsx-a11y oxlint rules as errors (#42006) Co-authored-by: Claude Opus 4.8 --- superset-frontend/oxlint.json | 28 +++++++++++++++---- .../src/components/FaveStar/index.tsx | 1 + .../src/components/Modal/Modal.tsx | 2 ++ .../src/components/RefreshLabel/index.tsx | 1 + .../src/DeckGLContainer.test.tsx | 1 + .../TableExploreTree/TreeNodeRenderer.tsx | 2 -- .../DrillDetail/DrillDetailTableControls.tsx | 1 + .../ScopingModal/ChartsScopingListPanel.tsx | 1 + .../nativeFilters/FilterBar/Vertical.tsx | 1 + .../FilterTitleContainer.tsx | 1 + .../FiltersConfigForm/DependencyList.tsx | 2 +- .../FiltersConfigModal/ItemTitleContainer.tsx | 1 + .../components/DataTableControls.tsx | 1 + .../components/ExploreViewContainer/index.tsx | 1 + .../DndColumnSelectPopoverTitle.tsx | 1 + .../DndColumnSelectControl/Option.tsx | 1 + .../AdhocMetricEditPopoverTitle.tsx | 6 +++- .../controls/OptionControls/index.tsx | 1 + .../features/queries/QueryPreviewModal.tsx | 2 ++ 19 files changed, 46 insertions(+), 9 deletions(-) diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json index 27563d81c5e..1516d96689c 100644 --- a/superset-frontend/oxlint.json +++ b/superset-frontend/oxlint.json @@ -52,10 +52,6 @@ // require-default-props, sort-comp, static-property-placement // (prefer-stateless-function / function-component-definition // are represented by react/prefer-function-component below) - // jsx-a11y: interactive-supports-focus, - // no-interactive-element-to-noninteractive-role, - // no-noninteractive-element-interactions, - // no-noninteractive-element-to-interactive-role // typescript: naming-convention // unicorn: prevent-abbreviations @@ -196,6 +192,7 @@ // === JSX-a11y rules === "jsx-a11y/alt-text": "error", + "jsx-a11y/anchor-ambiguous-text": "error", "jsx-a11y/anchor-has-content": "error", "jsx-a11y/anchor-is-valid": "error", "jsx-a11y/aria-activedescendant-has-tabindex": "error", @@ -203,20 +200,31 @@ "jsx-a11y/aria-proptypes": "error", "jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }], "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/autocomplete-valid": "error", "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/control-has-associated-label": "error", "jsx-a11y/heading-has-content": "error", "jsx-a11y/html-has-lang": "error", "jsx-a11y/iframe-has-title": "error", "jsx-a11y/img-redundant-alt": "error", + "jsx-a11y/interactive-supports-focus": "error", "jsx-a11y/label-has-associated-control": "error", "jsx-a11y/lang": "error", "jsx-a11y/media-has-caption": "error", - "jsx-a11y/mouse-events-have-key-events": "off", + "jsx-a11y/mouse-events-have-key-events": "error", "jsx-a11y/no-access-key": "error", + "jsx-a11y/no-aria-hidden-on-focusable": "error", "jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }], "jsx-a11y/no-distracting-elements": "error", + "jsx-a11y/no-interactive-element-to-noninteractive-role": "error", + "jsx-a11y/no-noninteractive-element-interactions": "error", + "jsx-a11y/no-noninteractive-element-to-interactive-role": "error", "jsx-a11y/no-noninteractive-tabindex": "error", "jsx-a11y/no-redundant-roles": "error", + // TODO: Graduate to "error" — remaining violations are event-boundary + // wrappers (divs/spans whose handlers only stopPropagation) and test + // scaffolding where adding a role is semantically wrong. Needs a + // dedicated pass; see PR enabling the other jsx-a11y interactive rules. "jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/role-has-required-aria-props": "error", "jsx-a11y/role-supports-aria-props": "error", @@ -289,6 +297,16 @@ "rules": { "jsx-a11y/no-redundant-roles": "off" } + }, + { + // The pivot table intentionally exposes `role="grid"` on its + // for interactive grid semantics (asserted by tableRenders.test.tsx). + "files": [ + "plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx" + ], + "rules": { + "jsx-a11y/no-noninteractive-element-to-interactive-role": "off" + } } ], "ignorePatterns": [ diff --git a/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx index 308e44e4630..fbfb9c5e4a1 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx @@ -60,6 +60,7 @@ export const FaveStar = ({ className="fave-unfave-icon" data-test="fave-unfave-icon" role="button" + tabIndex={0} > {isStarred ? ( dragDisabled && setDragDisabled(false)} onMouseOut={() => !dragDisabled && setDragDisabled(true)} + onFocus={() => dragDisabled && setDragDisabled(false)} + onBlur={() => !dragDisabled && setDragDisabled(true)} > {title} diff --git a/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx index 1c01ca63acd..c5c162a5098 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx @@ -36,6 +36,7 @@ const RefreshLabel = ({ ({ cursor: 'pointer', diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx b/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx index 21c30ef8a37..b035d2ee87d 100644 --- a/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx +++ b/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx @@ -42,6 +42,7 @@ jest.mock('react-map-gl/maplibre', () => ({