diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json
index 1516d96689c..876a0d9cab8 100644
--- a/superset-frontend/oxlint.json
+++ b/superset-frontend/oxlint.json
@@ -201,7 +201,7 @@
"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/click-events-have-key-events": "error",
"jsx-a11y/control-has-associated-label": "error",
"jsx-a11y/heading-has-content": "error",
"jsx-a11y/html-has-lang": "error",
diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx
index 123d5349505..06c6584d592 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { render, screen, userEvent } from '@superset-ui/core/spec';
+import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec';
import { Icons } from '@superset-ui/core/components/Icons';
import { ActionButton } from '.';
@@ -45,6 +45,18 @@ test('calls onClick when clicked', async () => {
expect(onClick).toHaveBeenCalledTimes(1);
});
+test('calls onClick when activated with the keyboard', () => {
+ const onClick = jest.fn();
+ render();
+
+ const button = screen.getByRole('button');
+ fireEvent.keyDown(button, { key: 'Enter' });
+ expect(onClick).toHaveBeenCalledTimes(1);
+
+ fireEvent.keyDown(button, { key: ' ' });
+ expect(onClick).toHaveBeenCalledTimes(2);
+});
+
test('renders with tooltip when tooltip prop is provided', async () => {
const tooltipText = 'This is a tooltip';
render();
diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx
index 336b8ca3d48..3e56efbc265 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
+import { handleKeyboardActivation } from '../../utils';
import type { ReactElement, ReactNode } from 'react';
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
@@ -55,6 +56,7 @@ export const ActionButton = ({
className="action-button"
data-test={label}
onClick={onClick}
+ onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
>
{icon}
diff --git a/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx
index 4f477437027..2e94712f513 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx
@@ -16,12 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '../../utils';
import {
forwardRef,
ForwardedRef,
useState,
ReactNode,
- MouseEvent,
+ SyntheticEvent,
} from 'react';
import { Button } from '../Button';
@@ -96,7 +97,7 @@ export const ModalTrigger = forwardRef(
onExit?.();
};
- const open = (e: MouseEvent) => {
+ const open = (e: SyntheticEvent) => {
e.preventDefault();
beforeOpen?.();
setShowModal(true);
@@ -126,7 +127,13 @@ export const ModalTrigger = forwardRef(
)}
{!isButton && (
-
+
{triggerNode}
)}
diff --git a/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx
index 39009eba76a..284bc60c824 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { MouseEventHandler, ReactNode } from 'react';
+import { handleKeyboardActivation } from '../../utils';
+import { ReactNode, SyntheticEvent } from 'react';
import { css, useTheme } from '@apache-superset/core/theme';
import { Icons } from '@superset-ui/core/components/Icons';
import { Tooltip } from '../Tooltip';
@@ -24,7 +25,10 @@ import { Tooltip } from '../Tooltip';
export interface PopoverSectionProps {
title: string;
isSelected?: boolean;
- onSelect?: MouseEventHandler
;
+ // `SyntheticEvent` (rather than `MouseEventHandler`) so the same callback
+ // can be reused as the keyboard-activation handler via
+ // `handleKeyboardActivation`, which invokes it with a `KeyboardEvent`.
+ onSelect?: (event: SyntheticEvent) => void;
info?: string;
children?: ReactNode;
}
@@ -48,6 +52,7 @@ export default function PopoverSection({
role="button"
tabIndex={0}
onClick={onSelect}
+ onKeyDown={onSelect ? handleKeyboardActivation(onSelect) : undefined}
css={css`
display: flex;
align-items: center;
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts
new file mode 100644
index 00000000000..79d0cc6b13b
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { KeyboardEvent } from 'react';
+import { handleKeyboardActivation } from './handleKeyboardActivation';
+
+const makeEvent = (key: string, repeat = false) => {
+ const preventDefault = jest.fn();
+ return {
+ event: { key, repeat, preventDefault } as unknown as KeyboardEvent,
+ preventDefault,
+ };
+};
+
+test('invokes the callback and prevents default on Enter', () => {
+ const callback = jest.fn();
+ const { event, preventDefault } = makeEvent('Enter');
+ handleKeyboardActivation(callback)(event);
+ expect(callback).toHaveBeenCalledWith(event);
+ expect(preventDefault).toHaveBeenCalled();
+});
+
+test('invokes the callback and prevents default on Space', () => {
+ const callback = jest.fn();
+ const { event, preventDefault } = makeEvent(' ');
+ handleKeyboardActivation(callback)(event);
+ expect(callback).toHaveBeenCalledWith(event);
+ expect(preventDefault).toHaveBeenCalled();
+});
+
+test('ignores other keys', () => {
+ const callback = jest.fn();
+ const { event, preventDefault } = makeEvent('a');
+ handleKeyboardActivation(callback)(event);
+ expect(callback).not.toHaveBeenCalled();
+ expect(preventDefault).not.toHaveBeenCalled();
+});
+
+test('ignores auto-repeat keydown events fired while a key is held', () => {
+ const callback = jest.fn();
+ const { event, preventDefault } = makeEvent('Enter', true);
+ handleKeyboardActivation(callback)(event);
+ expect(callback).not.toHaveBeenCalled();
+ // preventDefault still fires on the repeat event itself, matching the
+ // non-repeat Enter case above.
+ expect(preventDefault).toHaveBeenCalled();
+});
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts
new file mode 100644
index 00000000000..d77fa6f58f8
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { KeyboardEvent } from 'react';
+
+/**
+ * Builds an `onKeyDown` handler that invokes `callback` when the user presses
+ * Enter or Space, mirroring a click for keyboard users. Pair it with an
+ * element's `onClick` so `role="button"` (or similar) controls are operable
+ * from the keyboard, satisfying `jsx-a11y/click-events-have-key-events`.
+ *
+ *
+ */
+export function handleKeyboardActivation(
+ callback: (event: KeyboardEvent) => void,
+) {
+ return (event: KeyboardEvent) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ // Prevent the page from scrolling on Space and stop any duplicate
+ // default activation on Enter.
+ event.preventDefault();
+ // Ignore auto-repeat keydown events fired while the key is held, so
+ // a long press activates the callback once, matching a mouse click.
+ if (event.repeat) {
+ return;
+ }
+ callback(event);
+ }
+ };
+}
+
+export default handleKeyboardActivation;
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/index.ts b/superset-frontend/packages/superset-ui-core/src/utils/index.ts
index f947bdfd57e..797c7ab561f 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/index.ts
@@ -27,6 +27,7 @@ export { default as makeSingleton } from './makeSingleton';
export { default as promiseTimeout } from './promiseTimeout';
export { default as removeDuplicates } from './removeDuplicates';
export { default as withLabel } from './withLabel';
+export { handleKeyboardActivation } from './handleKeyboardActivation';
export { lruCache } from './lruCache';
export { getSelectedText } from './getSelectedText';
export * from './featureFlags';
diff --git a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx
index cb6e58e5491..e5147585cae 100644
--- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx
+++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx
@@ -19,7 +19,8 @@
* under the License.
*/
-import { useRef, useState, useEffect } from 'react';
+import { handleKeyboardActivation } from '@superset-ui/core';
+import { useRef, useState, useEffect, SyntheticEvent } from 'react';
import { t } from '@apache-superset/core/translation';
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
import { Column } from '@superset-ui/core/components/ThemedAgGridReact';
@@ -186,7 +187,10 @@ const CustomHeader: React.FC = ({
return undefined;
}, [lastFilteredColumn, colId, lastFilteredInputPosition]);
- const handleMenuClick = (e: React.MouseEvent) => {
+ // `SyntheticEvent` (rather than `MouseEvent`) so this callback can also be
+ // used as the keyboard-activation handler via `handleKeyboardActivation`,
+ // which invokes it with a `KeyboardEvent`.
+ const handleMenuClick = (e: SyntheticEvent) => {
e.stopPropagation();
setMenuVisible(!isMenuVisible);
};
@@ -201,17 +205,35 @@ const CustomHeader: React.FC = ({
const menuContent = (
{shouldShowAsc && (
- applySort('asc')} className="menu-item">
+
applySort('asc')}
+ onKeyDown={handleKeyboardActivation(() => applySort('asc'))}
+ className="menu-item"
+ >
{t('Sort Ascending')}
)}
{shouldShowDesc && (
-
applySort('desc')} className="menu-item">
+
applySort('desc')}
+ onKeyDown={handleKeyboardActivation(() => applySort('desc'))}
+ className="menu-item"
+ >
{t('Sort Descending')}
)}
{currentSort && currentSort?.colId === colId && (
-
+
↻ {t('Clear Sort')}
)}
@@ -247,7 +269,13 @@ const CustomHeader: React.FC
= ({
isOpen={isMenuVisible}
onClose={() => setMenuVisible(false)}
>
-
+
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
index d3e58763827..1d635803498 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
@@ -20,13 +20,14 @@
import {
ReactNode,
MouseEvent,
+ SyntheticEvent,
useState,
useCallback,
useRef,
useMemo,
useEffect,
} from 'react';
-import { safeHtmlSpan } from '@superset-ui/core';
+import { safeHtmlSpan, handleKeyboardActivation } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { supersetTheme } from '@apache-superset/core/theme';
import PropTypes from 'prop-types';
@@ -154,7 +155,10 @@ function displayCell(value: unknown, allowRenderHtml?: boolean): ReactNode {
function displayHeaderCell(
needToggle: boolean,
ArrowIcon: ReactNode,
- onArrowClick: ((e: MouseEvent
) => void) | null,
+ // `SyntheticEvent` (rather than `MouseEvent`) so this callback can also be
+ // used as the keyboard-activation handler via `handleKeyboardActivation`,
+ // which invokes it with a `KeyboardEvent`.
+ onArrowClick: ((e: SyntheticEvent) => void) | null,
value: unknown,
namesMapping: Record,
allowRenderHtml?: boolean,
@@ -172,6 +176,9 @@ function displayHeaderCell(
tabIndex={0}
className="toggle"
onClick={onArrowClick || undefined}
+ onKeyDown={
+ onArrowClick ? handleKeyboardActivation(onArrowClick) : undefined
+ }
>
{ArrowIcon}
@@ -462,7 +469,7 @@ export function TableRenderer(props: TableRendererProps) {
);
const toggleRowKey = useCallback(
- (flatRowKey: string) => (e: MouseEvent) => {
+ (flatRowKey: string) => (e: SyntheticEvent) => {
e.stopPropagation();
setCollapsedRows(state => ({
...state,
@@ -473,7 +480,7 @@ export function TableRenderer(props: TableRendererProps) {
);
const toggleColKey = useCallback(
- (flatColKey: string) => (e: MouseEvent) => {
+ (flatColKey: string) => (e: SyntheticEvent) => {
e.stopPropagation();
setCollapsedCols(state => ({
...state,
@@ -1046,6 +1053,7 @@ export function TableRenderer(props: TableRendererProps) {
onClick={e => {
e.stopPropagation();
}}
+ onKeyDown={e => e.stopPropagation()}
aria-label={
activeSortColumn === i
? `Sorted by ${columnName} ${sortingOrder[i] === 'asc' ? 'ascending' : 'descending'}`
diff --git a/superset-frontend/spec/helpers/shim.tsx b/superset-frontend/spec/helpers/shim.tsx
index 0bff723e4ca..b1c243357b9 100644
--- a/superset-frontend/spec/helpers/shim.tsx
+++ b/superset-frontend/spec/helpers/shim.tsx
@@ -119,7 +119,7 @@ jest.mock('@superset-ui/core/components/Icons/AsyncIcon', () => ({
const label =
ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || '';
return (
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
= ({
e.stopPropagation()}
+ onKeyDown={e => e.stopPropagation()}
>
= ({
node.toggle();
}
}}
+ onKeyDown={handleKeyboardActivation(() => {
+ if (node.isLeaf) {
+ node.select();
+ } else {
+ node.toggle();
+ }
+ })}
>
= ({
e.stopPropagation()}
+ onKeyDown={e => e.stopPropagation()}
>
{pinnedSchemas.has(schema) && (
@@ -306,6 +316,7 @@ const TreeNodeRenderer: React.FC
= ({
e.stopPropagation()}
+ onKeyDown={e => e.stopPropagation()}
>
{isPinned && (
diff --git a/superset-frontend/src/components/Chart/Chart.tsx b/superset-frontend/src/components/Chart/Chart.tsx
index 1519837c747..2fcdeb6c7f4 100644
--- a/superset-frontend/src/components/Chart/Chart.tsx
+++ b/superset-frontend/src/components/Chart/Chart.tsx
@@ -30,6 +30,7 @@ import {
type FilterState,
type JsonObject,
type AgGridChartState,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { styled } from '@apache-superset/core/theme';
import type { ChartState, Datasource, ChartStatus } from 'src/explore/types';
@@ -465,7 +466,12 @@ function Chart({
{t(
'Click on "Create chart" button in the control panel on the left to preview a visualization or',
)}{' '}
-
+ onQuery?.())}
+ >
{t('click here')}
.
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
index 57149ee55e0..dd6f747f2c5 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
@@ -17,7 +17,14 @@
* under the License.
*/
-import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import {
+ SyntheticEvent,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
import { t } from '@apache-superset/core/translation';
import {
BinaryQueryObjectFilterClause,
@@ -28,6 +35,7 @@ import {
isDefined,
ContextMenuFilters,
AdhocFilter,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { Alert } from '@apache-superset/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
@@ -554,6 +562,12 @@ export default function DrillByModal({
items={breadcrumbItems}
itemRender={(route, _, routes, paths) => {
const isLastElement = routes.indexOf(route) === routes.length - 1;
+ // `route.onClick` is typed by antd as a `MouseEventHandler`, but
+ // the underlying handler ignores its argument, so it's safe to
+ // broaden it to an optional `SyntheticEvent` callback here to
+ // reuse it as the keyboard-activation handler below.
+ const onRouteClick = route.onClick as
+ ((event?: SyntheticEvent) => void) | undefined;
return isLastElement ? (
{route.title}
@@ -564,7 +578,8 @@ export default function DrillByModal({
data-test="drill-by-breadcrumb-item"
role="button"
tabIndex={0}
- onClick={route.onClick}
+ onClick={onRouteClick}
+ onKeyDown={handleKeyboardActivation(() => onRouteClick?.())}
css={css`
cursor: pointer;
`}
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx
index d8053a8d600..d331cf528e0 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx
@@ -217,6 +217,7 @@ export const DrillBySubmenu = ({
role="menu"
tabIndex={0}
data-test="drill-by-submenu"
+ onKeyDown={e => e.stopPropagation()}
css={css`
width: 220px;
max-width: 220px;
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
index d5a9034a023..ddb0f6d45dd 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
@@ -36,6 +36,7 @@ import {
SupersetClient,
getClientErrorObject,
getExtensionsRegistry,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/common';
import { t } from '@apache-superset/core/translation';
@@ -1760,6 +1761,7 @@ function DatasourceEditor({
role="button"
tabIndex={0}
onClick={onChangeEditMode}
+ onKeyDown={handleKeyboardActivation(onChangeEditMode)}
>
{isEditMode ? (
= ({
role="button"
tabIndex={0}
onClick={toggleDescription}
+ onKeyDown={handleKeyboardActivation(toggleDescription)}
style={{ textDecoration: 'underline', cursor: 'pointer' }}
>
{isDescriptionVisible ? t('See less') : t('See more')}
@@ -127,7 +129,12 @@ export const ErrorAlert: React.FC = ({
return (
<>
- setShowModal(true)} tabIndex={0}>
+ setShowModal(true)}
+ onKeyDown={handleKeyboardActivation(() => setShowModal(true))}
+ tabIndex={0}
+ >
{renderTrigger()}
diff --git a/superset-frontend/src/components/ListView/ListView.test.tsx b/superset-frontend/src/components/ListView/ListView.test.tsx
index e2d0dec4bfc..2b10a1245d1 100644
--- a/superset-frontend/src/components/ListView/ListView.test.tsx
+++ b/superset-frontend/src/components/ListView/ListView.test.tsx
@@ -16,7 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { render, screen, within, waitFor } from 'spec/helpers/testing-library';
+import {
+ render,
+ screen,
+ within,
+ waitFor,
+ fireEvent,
+} from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { QueryParamProvider } from 'use-query-params';
import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
@@ -359,4 +365,24 @@ describe('ListView', () => {
expect(mockedPropsComprehensive.fetchData).toHaveBeenCalled();
});
+
+ test('switches view mode via keyboard activation of the toggle buttons', () => {
+ const { container } = factory({
+ renderCard: jest.fn(),
+ data: [],
+ count: 0,
+ initialSort: [{ id: 'something' }],
+ });
+
+ const [cardToggle, tableToggle] =
+ container.querySelectorAll('.toggle-button');
+
+ expect(screen.getByTestId('empty-state')).toHaveClass('card');
+
+ fireEvent.keyDown(tableToggle, { key: 'Enter' });
+ expect(screen.getByTestId('empty-state')).toHaveClass('table');
+
+ fireEvent.keyDown(cardToggle, { key: ' ' });
+ expect(screen.getByTestId('empty-state')).toHaveClass('card');
+ });
});
diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx
index a88b2ebb736..f8129b50009 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { Alert } from '@apache-superset/core/components';
import { styled } from '@apache-superset/core/theme';
@@ -255,6 +256,7 @@ const ViewModeToggle = ({
e.currentTarget.blur();
setMode('card');
}}
+ onKeyDown={handleKeyboardActivation(() => setMode('card'))}
className={cx('toggle-button', { active: mode === 'card' })}
>
@@ -269,6 +271,7 @@ const ViewModeToggle = ({
e.currentTarget.blur();
setMode('table');
}}
+ onKeyDown={handleKeyboardActivation(() => setMode('table'))}
className={cx('toggle-button', { active: mode === 'table' })}
>
@@ -509,6 +512,9 @@ export function ListView({
tabIndex={0}
className="deselect-all"
onClick={() => toggleAllRowsSelected(false)}
+ onKeyDown={handleKeyboardActivation(() =>
+ toggleAllRowsSelected(false),
+ )}
>
{t('Deselect all')}
@@ -544,6 +550,9 @@ export function ListView({
tabIndex={0}
className="tag-btn"
onClick={() => setShowBulkTagModal(true)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setShowBulkTagModal(true),
+ )}
>
{t('Add Tag')}
diff --git a/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx b/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx
index 136c44425b9..6574bb69386 100644
--- a/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx
+++ b/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx
@@ -106,6 +106,7 @@ export default function URLShortLinkButton({
onClick={e => {
e.stopPropagation();
}}
+ onKeyDown={e => e.stopPropagation()}
>
{
role="button"
tabIndex={0}
onClick={() => dispatch(setEditMode(true))}
+ onKeyDown={handleKeyboardActivation(() =>
+ dispatch(setEditMode(true)),
+ )}
>
{t('edit mode')}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
index 367072fbab9..c68cf9953e4 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { ReactNode, FC, memo } from 'react';
import { getFilterBarTestId } from '../utils';
@@ -32,6 +33,7 @@ export const FilterConfigurationLink: FC = ({
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
index e5420ad1b63..c3026f18efe 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { forwardRef, useCallback, useState } from 'react';
import { t } from '@apache-superset/core/translation';
@@ -199,6 +200,7 @@ const FilterTitleContainer = forwardRef
(
e.preventDefault();
restoreFilter(id);
}}
+ onKeyDown={handleKeyboardActivation(() => restoreFilter(id))}
>
{t('Undo?')}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
index 388421b5ac1..7904835fc4c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { forwardRef, useState } from 'react';
import { t } from '@apache-superset/core/translation';
@@ -167,6 +168,7 @@ const ItemTitleContainer = forwardRef(
e.preventDefault();
restoreItem(id);
}}
+ onKeyDown={handleKeyboardActivation(() => restoreItem(id))}
>
{t('Undo?')}
diff --git a/superset-frontend/src/explore/components/ControlHeader.tsx b/superset-frontend/src/explore/components/ControlHeader.tsx
index a2bbf047c31..fd62cbaf739 100644
--- a/superset-frontend/src/explore/components/ControlHeader.tsx
+++ b/superset-frontend/src/explore/components/ControlHeader.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { FC, ReactNode } from 'react';
import { t } from '@apache-superset/core/translation';
import { css, useTheme, SupersetTheme } from '@apache-superset/core/theme';
@@ -139,6 +140,7 @@ const ControlHeader: FC = ({
role="button"
tabIndex={0}
onClick={onClick}
+ onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
style={{ cursor: onClick ? 'pointer' : '' }}
>
{label}
diff --git a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
index 55994e58d82..6c40bba70d9 100644
--- a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
+++ b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
@@ -18,7 +18,11 @@
*/
import { useCallback, useEffect, useMemo, useState, MouseEvent } from 'react';
import { t } from '@apache-superset/core/translation';
-import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
+import {
+ isFeatureEnabled,
+ FeatureFlag,
+ handleKeyboardActivation,
+} from '@superset-ui/core';
import { styled } from '@apache-superset/core/theme';
import { Icons } from '@superset-ui/core/components/Icons';
import Tabs from '@superset-ui/core/components/Tabs';
@@ -187,6 +191,9 @@ export const DataTablesPane = ({
role="button"
tabIndex={0}
onClick={() => handleCollapseChange(false)}
+ onKeyDown={handleKeyboardActivation(() =>
+ handleCollapseChange(false),
+ )}
>
{caretIcon}
@@ -195,6 +202,9 @@ export const DataTablesPane = ({
role="button"
tabIndex={0}
onClick={() => handleCollapseChange(true)}
+ onKeyDown={handleKeyboardActivation(() =>
+ handleCollapseChange(true),
+ )}
>
{caretIcon}
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
index 7e9d5244130..c72efc8c5fd 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
@@ -18,7 +18,12 @@
*/
import { useContext, useDeferredValue, useMemo, useState } from 'react';
import { t } from '@apache-superset/core/translation';
-import { DatasourceType, Metric, QueryFormData } from '@superset-ui/core';
+import {
+ DatasourceType,
+ Metric,
+ QueryFormData,
+ handleKeyboardActivation,
+} from '@superset-ui/core';
import { Alert } from '@apache-superset/core/components';
import { css, styled, useTheme } from '@apache-superset/core/theme';
@@ -290,6 +295,9 @@ export default function DataSourcePanel({
role="button"
tabIndex={0}
onClick={() => setShowSaveDatasetModal(true)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setShowSaveDatasetModal(true),
+ )}
className="add-dataset-alert-description"
>
{t('Create a dataset')}
diff --git a/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx b/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx
index 3145bda73a2..7e17b69dce9 100644
--- a/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx
+++ b/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx
@@ -30,6 +30,7 @@ import {
QueryFormData,
JsonObject,
getExtensionsRegistry,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { Alert } from '@apache-superset/core/components';
import { css, styled, useTheme } from '@apache-superset/core/theme';
@@ -396,6 +397,9 @@ const ExploreChartPanel = ({
role="button"
tabIndex={0}
onClick={() => setShowDatasetModal(true)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setShowDatasetModal(true),
+ )}
css={{ textDecoration: 'underline' }}
>
{t('Create a dataset')}
@@ -420,7 +424,12 @@ const ExploreChartPanel = ({
{t(
'You updated the values in the control panel, but the chart was not updated automatically. Run the query by clicking on the "Update chart" button or',
)}{' '}
-
+ onQuery?.())}
+ >
{t('click here')}
.
diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
index aebaa64da17..b0ca4ac0868 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
@@ -37,6 +37,7 @@ import {
MatrixifyFormData,
DatasourceType,
ensureIsArray,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import {
ControlStateMapping,
@@ -998,6 +999,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
tabIndex={0}
className="action-button"
onClick={toggleCollapse}
+ onKeyDown={handleKeyboardActivation(toggleCollapse)}
>
({
},
}}
onClick={() => setShowAllColumns(!showAllColumns)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setShowAllColumns(!showAllColumns),
+ )}
>
{showAllColumns ? (
<>
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
index 1e69be2c45e..16d0aa344cc 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
@@ -35,6 +35,7 @@ import {
DatasourceType,
Metric,
QueryFormMetric,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { styled, css } from '@apache-superset/core/theme';
import { ColumnMeta, isSavedExpression } from '@superset-ui/chart-controls';
@@ -476,6 +477,9 @@ const ColumnSelectPopover = ({
role="button"
tabIndex={0}
onClick={setDatasetAndClose}
+ onKeyDown={handleKeyboardActivation(
+ setDatasetAndClose,
+ )}
>
{t('Create a dataset')}
{' '}
@@ -487,6 +491,9 @@ const ColumnSelectPopover = ({
role="button"
tabIndex={0}
onClick={setDatasetAndClose}
+ onKeyDown={handleKeyboardActivation(
+ setDatasetAndClose,
+ )}
>
{t('Create a dataset')}
{' '}
@@ -521,6 +528,9 @@ const ColumnSelectPopover = ({
role="button"
tabIndex={0}
onClick={setDatasetAndClose}
+ onKeyDown={handleKeyboardActivation(
+ setDatasetAndClose,
+ )}
>
{t('Create a dataset')}
{' '}
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
index 05c3cd3744c..38b3a47a73d 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { ChangeEvent, useCallback, useState } from 'react';
import { t } from '@apache-superset/core/translation';
import { styled, useTheme } from '@apache-superset/core/theme';
@@ -96,6 +97,7 @@ export const DndColumnSelectPopoverTitle = ({
onMouseOut={onMouseOut}
onFocus={onMouseOver}
onClick={onClick}
+ onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
onBlur={onBlur}
role="button"
tabIndex={0}
diff --git a/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx b/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx
index 72f519ff7d8..ce99c88acd3 100644
--- a/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx
+++ b/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { Icons } from '@superset-ui/core/components/Icons';
import { Button } from '@superset-ui/core/components';
import { Tag } from 'src/components';
@@ -47,6 +48,7 @@ export const LayerTreeItem: FC = ({
@@ -55,6 +57,7 @@ export const LayerTreeItem: FC = ({
diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx
index ccbd3a2deab..ce0fea1968b 100644
--- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx
+++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx
@@ -19,7 +19,12 @@
/* eslint-disable camelcase */
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
-import { isDefined, ensureIsArray, DatasourceType } from '@superset-ui/core';
+import {
+ isDefined,
+ ensureIsArray,
+ DatasourceType,
+ handleKeyboardActivation,
+} from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import type { editors } from '@apache-superset/core';
import { styled } from '@apache-superset/core/theme';
@@ -498,6 +503,10 @@ function AdhocMetricEditPopover({
handleDatasetModal?.(true);
onClose();
}}
+ onKeyDown={handleKeyboardActivation(() => {
+ handleDatasetModal?.(true);
+ onClose();
+ })}
>
{t('Create a dataset')}
diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
index 57d5ed8e3c2..e9cc283f458 100644
--- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
+++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import {
ChangeEventHandler,
FocusEvent,
@@ -120,6 +121,7 @@ const AdhocMetricEditPopoverTitle: FC = ({
onMouseOut={handleMouseOut}
onFocus={handleMouseOver}
onClick={handleClick}
+ onKeyDown={handleKeyboardActivation(handleClick)}
onBlur={handleBlur}
role="button"
tabIndex={0}
diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx
index ad11bd73da9..10c9fceff3f 100644
--- a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx
@@ -18,7 +18,10 @@
*/
import { useCallback, useState } from 'react';
import { t } from '@apache-superset/core/translation';
-import { getChartMetadataRegistry } from '@superset-ui/core';
+import {
+ getChartMetadataRegistry,
+ handleKeyboardActivation,
+} from '@superset-ui/core';
import { css, styled, SupersetTheme } from '@apache-superset/core/theme';
import { usePluginContext } from 'src/components';
import { Icons, Modal } from '@superset-ui/core/components';
@@ -112,7 +115,12 @@ const VizTypeControl = ({
color: ${theme.colorTextTertiary};
`}
>
-
+
{t('View all charts')}
diff --git a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
index 564dba2cc25..244f5f5f120 100644
--- a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
+++ b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
@@ -33,6 +33,7 @@ import {
JsonResponse,
SupersetClient,
isFeatureEnabled,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { styled, useTheme } from '@apache-superset/core/theme';
import { Icons } from '@superset-ui/core/components/Icons';
@@ -580,6 +581,7 @@ export const NotificationMethod: FunctionComponent = ({
tabIndex={0}
className="delete-button"
onClick={() => onRemove(index)}
+ onKeyDown={handleKeyboardActivation(() => onRemove(index))}
aria-label={t('Remove notification method')}
>
@@ -782,6 +784,7 @@ export const NotificationMethod: FunctionComponent = ({
role="button"
tabIndex={0}
onClick={() => setCcVisible(true)}
+ onKeyDown={handleKeyboardActivation(() => setCcVisible(true))}
style={{ display: ccVisible ? 'none' : 'inline-flex' }}
>
@@ -792,6 +795,9 @@ export const NotificationMethod: FunctionComponent = ({
role="button"
tabIndex={0}
onClick={() => setBccVisible(true)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setBccVisible(true),
+ )}
style={{ display: bccVisible ? 'none' : 'inline-flex' }}
>
diff --git a/superset-frontend/src/features/charts/ChartCard.tsx b/superset-frontend/src/features/charts/ChartCard.tsx
index 9a7c0569ed2..b55904f7144 100644
--- a/superset-frontend/src/features/charts/ChartCard.tsx
+++ b/superset-frontend/src/features/charts/ChartCard.tsx
@@ -17,7 +17,11 @@
* under the License.
*/
import { t } from '@apache-superset/core/translation';
-import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
+import {
+ isFeatureEnabled,
+ FeatureFlag,
+ handleKeyboardActivation,
+} from '@superset-ui/core';
import { css } from '@apache-superset/core/theme';
import { Link, useHistory } from 'react-router-dom';
import {
@@ -86,6 +90,7 @@ export default function ChartCard({
role="button"
tabIndex={0}
onClick={() => openChartEditModal(chart)}
+ onKeyDown={handleKeyboardActivation(() => openChartEditModal(chart))}
>
handleBulkChartExport([chart])}
+ onKeyDown={handleKeyboardActivation(() =>
+ handleBulkChartExport([chart]),
+ )}
>
openDashboardEditModal(dashboard)}
+ onKeyDown={handleKeyboardActivation(() =>
+ openDashboardEditModal(dashboard),
+ )}
data-test="dashboard-card-option-edit-button"
>
{t('Edit')}
@@ -96,6 +103,9 @@ function DashboardCard({
role="button"
tabIndex={0}
onClick={() => handleBulkDashboardExport([dashboard])}
+ onKeyDown={handleKeyboardActivation(() =>
+ handleBulkDashboardExport([dashboard]),
+ )}
className="action-button"
data-test="dashboard-card-option-export-button"
>
@@ -114,6 +124,7 @@ function DashboardCard({
tabIndex={0}
className="action-button"
onClick={() => onDelete(dashboard)}
+ onKeyDown={handleKeyboardActivation(() => onDelete(dashboard))}
data-test="dashboard-card-option-delete-button"
>
{t('Delete')}
diff --git a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx
index 6bf357a1f3b..78287663af4 100644
--- a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx
+++ b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { handleKeyboardActivation } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { Alert } from '@apache-superset/core/components';
import { styled } from '@apache-superset/core/theme';
@@ -216,34 +217,38 @@ const EXISTING_DATASET_DESCRIPTION = t(
);
const VIEW_DATASET = t('View Dataset');
-const renderExistingDatasetAlert = (dataset?: DatasetObject) => (
-
- {EXISTING_DATASET_DESCRIPTION}
- {
- if (dataset?.explore_url) {
- // `explore_url` is router-relative from the backend (rooted under
- // a subdirectory deployment); strip the root so openInNewTab's
- // ensureAppRoot re-prefixes it once rather than doubling it.
- openInNewTab(stripAppRoot(dataset.explore_url));
- }
- }}
- tabIndex={0}
- className="view-dataset-button"
- >
- {VIEW_DATASET}
-
- >
+const renderExistingDatasetAlert = (dataset?: DatasetObject) => {
+ const openExplore = () => {
+ if (dataset?.explore_url) {
+ // `explore_url` is router-relative from the backend (rooted under
+ // a subdirectory deployment); strip the root so openInNewTab's
+ // ensureAppRoot re-prefixes it once rather than doubling it.
+ openInNewTab(stripAppRoot(dataset.explore_url));
}
- />
-);
+ };
+ return (
+
+ {EXISTING_DATASET_DESCRIPTION}
+
+ {VIEW_DATASET}
+
+ >
+ }
+ />
+ );
+};
const DatasetPanel = ({
tableName,
diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx
index 45e65370170..00ea34af07e 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -25,6 +25,7 @@ import {
JsonResponse,
SupersetClient,
isMatrixifyEnabled,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { css, styled } from '@apache-superset/core/theme';
import { useState, useMemo, useCallback } from 'react';
@@ -544,6 +545,7 @@ function ChartList(props: ChartListProps) {
tabIndex={0}
className="action-button"
onClick={openEditModal}
+ onKeyDown={handleKeyboardActivation(openEditModal)}
>
@@ -561,6 +563,7 @@ function ChartList(props: ChartListProps) {
tabIndex={0}
className="action-button"
onClick={handleExport}
+ onKeyDown={handleKeyboardActivation(handleExport)}
>
@@ -589,6 +592,7 @@ function ChartList(props: ChartListProps) {
tabIndex={0}
className="action-button"
onClick={confirmDelete}
+ onKeyDown={handleKeyboardActivation(confirmDelete)}
>
diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx
index 100669050bb..91d3ef3bc80 100644
--- a/superset-frontend/src/pages/DashboardList/index.tsx
+++ b/superset-frontend/src/pages/DashboardList/index.tsx
@@ -21,6 +21,7 @@ import {
isFeatureEnabled,
FeatureFlag,
SupersetClient,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { styled } from '@apache-superset/core/theme';
import { useSelector } from 'react-redux';
@@ -503,6 +504,7 @@ function DashboardList(props: DashboardListProps) {
tabIndex={0}
className="action-button"
onClick={handleEdit}
+ onKeyDown={handleKeyboardActivation(handleEdit)}
>
@@ -520,6 +522,7 @@ function DashboardList(props: DashboardListProps) {
tabIndex={0}
className="action-button"
onClick={handleExport}
+ onKeyDown={handleKeyboardActivation(handleExport)}
>
@@ -548,6 +551,7 @@ function DashboardList(props: DashboardListProps) {
tabIndex={0}
className="action-button"
onClick={confirmDelete}
+ onKeyDown={handleKeyboardActivation(confirmDelete)}
>
setSlCurrentlyDeleting(original)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setSlCurrentlyDeleting(original),
+ )}
>
@@ -715,6 +719,9 @@ function DatabaseList({
onClick={() =>
setSlCurrentlyEditing(original.uuid ?? null)
}
+ onKeyDown={handleKeyboardActivation(() =>
+ setSlCurrentlyEditing(original.uuid ?? null),
+ )}
>
@@ -746,6 +753,7 @@ function DatabaseList({
tabIndex={0}
className="action-button"
onClick={handleEdit}
+ onKeyDown={handleKeyboardActivation(handleEdit)}
>
@@ -762,6 +770,7 @@ function DatabaseList({
tabIndex={0}
className="action-button"
onClick={handleExport}
+ onKeyDown={handleKeyboardActivation(handleExport)}
>
@@ -779,6 +788,7 @@ function DatabaseList({
tabIndex={0}
className="action-button"
onClick={handleSync}
+ onKeyDown={handleKeyboardActivation(handleSync)}
>
@@ -791,6 +801,7 @@ function DatabaseList({
className="action-button"
data-test="database-delete"
onClick={handleDelete}
+ onKeyDown={handleKeyboardActivation(handleDelete)}
>
= ({
tabIndex={0}
className="action-button"
onClick={() => handleSemanticViewDelete(original)}
+ onKeyDown={handleKeyboardActivation(() =>
+ handleSemanticViewDelete(original),
+ )}
>
@@ -886,6 +890,9 @@ const DatasetList: FunctionComponent = ({
tabIndex={0}
className="action-button"
onClick={() => setSvCurrentlyEditing(original)}
+ onKeyDown={handleKeyboardActivation(() =>
+ setSvCurrentlyEditing(original),
+ )}
>
@@ -932,6 +939,11 @@ const DatasetList: FunctionComponent = ({
tabIndex={0}
className={`action-button ${allowEdit ? '' : 'disabled'}`}
onClick={allowEdit ? handleEdit : undefined}
+ onKeyDown={
+ allowEdit
+ ? handleKeyboardActivation(handleEdit)
+ : undefined
+ }
>
@@ -949,6 +961,7 @@ const DatasetList: FunctionComponent = ({
tabIndex={0}
className="action-button"
onClick={handleExport}
+ onKeyDown={handleKeyboardActivation(handleExport)}
>
@@ -966,6 +979,7 @@ const DatasetList: FunctionComponent = ({
tabIndex={0}
className="action-button"
onClick={handleDuplicate}
+ onKeyDown={handleKeyboardActivation(handleDuplicate)}
>
@@ -983,6 +997,7 @@ const DatasetList: FunctionComponent = ({
tabIndex={0}
className="action-button"
onClick={handleDelete}
+ onKeyDown={handleKeyboardActivation(handleDelete)}
>
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
index 4d77911ba1d..689c493e49e 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { t } from '@apache-superset/core/translation';
-import { SupersetClient } from '@superset-ui/core';
+import { SupersetClient, handleKeyboardActivation } from '@superset-ui/core';
import { useCallback, useMemo, useState } from 'react';
import { ConfirmStatusChange, Tooltip } from '@superset-ui/core/components';
import {
@@ -209,6 +209,7 @@ function RowLevelSecurityList(props: RLSProps) {
tabIndex={0}
className="action-button"
onClick={handleEdit}
+ onKeyDown={handleKeyboardActivation(handleEdit)}
>
@@ -236,6 +237,7 @@ function RowLevelSecurityList(props: RLSProps) {
tabIndex={0}
className="action-button"
onClick={confirmDelete}
+ onKeyDown={handleKeyboardActivation(confirmDelete)}
>
diff --git a/superset-frontend/src/pages/TaskList/index.tsx b/superset-frontend/src/pages/TaskList/index.tsx
index 41b25d65602..3acac2d5177 100644
--- a/superset-frontend/src/pages/TaskList/index.tsx
+++ b/superset-frontend/src/pages/TaskList/index.tsx
@@ -21,6 +21,7 @@ import {
FeatureFlag,
isFeatureEnabled,
SupersetClient,
+ handleKeyboardActivation,
} from '@superset-ui/core';
import { useTheme } from '@apache-superset/core/theme';
import { t } from '@apache-superset/core/translation';
@@ -519,6 +520,9 @@ function TaskList({ addDangerToast, addSuccessToast, user }: TaskListProps) {
tabIndex={0}
className="action-button"
onClick={() => openCancelModal(original)}
+ onKeyDown={handleKeyboardActivation(() =>
+ openCancelModal(original),
+ )}
>