You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/10 20:09:24 UTC
(superset) branch fix/followup-41329-follow-up-from-41329-fix-sqllab-replace- created (now beb8643bc4e)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a change to branch fix/followup-41329-follow-up-from-41329-fix-sqllab-replace-
in repository https://gitbox.apache.org/repos/asf/superset.git
at beb8643bc4e fix(sqllab): make MenuDotsDropdown trigger focusable for tab rename focus return
This branch includes the following new commits:
new beb8643bc4e fix(sqllab): make MenuDotsDropdown trigger focusable for tab rename focus return
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: fix(sqllab): make MenuDotsDropdown trigger focusable for tab rename focus return
Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch fix/followup-41329-follow-up-from-41329-fix-sqllab-replace-
in repository https://gitbox.apache.org/repos/asf/superset.git
commit beb8643bc4eac9949f95afa711f45a75bf1e9e8d
Author: rusackas <ev...@rusackas.com>
AuthorDate: Mon Aug 10 13:08:56 2026 -0700
fix(sqllab): make MenuDotsDropdown trigger focusable for tab rename focus return
Follow-up to #41329, which returned focus to the whole SQL Lab tab header
wrapper after closing the rename-tab modal (the author explicitly deferred
making the menu trigger itself focusable). The "..." trigger is now a
focusable, ref-forwarding element, so focus returns to the specific
"Rename tab" menu trigger instead of the wrapper around it.
Co-Authored-By: Claude <no...@anthropic.com>
---
.../src/components/Dropdown/Dropdown.test.tsx | 21 ++++++++++++++++++++-
.../src/components/Dropdown/index.tsx | 17 +++++++++--------
.../SqlEditorTabHeader/SqlEditorTabHeader.test.tsx | 8 ++++----
.../SqlLab/components/SqlEditorTabHeader/index.tsx | 15 ++++++---------
4 files changed, 39 insertions(+), 22 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/components/Dropdown/Dropdown.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Dropdown/Dropdown.test.tsx
index 5f1d44bae99..ac68f8d7bbf 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Dropdown/Dropdown.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Dropdown/Dropdown.test.tsx
@@ -17,12 +17,31 @@
* under the License.
*/
+import { createRef } from 'react';
import { render, fireEvent, screen } from '@superset-ui/core/spec';
-import { NoAnimationDropdown } from '.';
+import { MenuDotsDropdown, NoAnimationDropdown } from '.';
const props = {
overlay: <div>Test Overlay</div>,
};
+
+describe('MenuDotsDropdown', () => {
+ test('renders a focusable trigger', () => {
+ render(<MenuDotsDropdown {...props} />);
+ expect(screen.getByTestId('dropdown-trigger')).toHaveAttribute(
+ 'tabIndex',
+ '0',
+ );
+ });
+
+ test('forwards a ref to the trigger so callers can focus it programmatically', () => {
+ const ref = createRef<HTMLDivElement>();
+ render(<MenuDotsDropdown {...props} ref={ref} />);
+ ref.current?.focus();
+ expect(screen.getByTestId('dropdown-trigger')).toHaveFocus();
+ });
+});
+
describe('NoAnimationDropdown', () => {
test('requires children', () => {
expect(() => {
diff --git a/superset-frontend/packages/superset-ui-core/src/components/Dropdown/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/Dropdown/index.tsx
index d5092353e43..8909599b354 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Dropdown/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Dropdown/index.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ReactElement, cloneElement } from 'react';
+import { ReactElement, cloneElement, forwardRef } from 'react';
import { Dropdown as AntdDropdown, DropdownProps } from 'antd';
import { styled } from '@apache-superset/core/theme';
@@ -84,17 +84,18 @@ const RenderIcon = (
return component;
};
-export const MenuDotsDropdown = ({
- overlay,
- iconOrientation = IconOrientation.Vertical,
- ...rest
-}: MenuDotsDropdownProps) => (
+export const MenuDotsDropdown = forwardRef<
+ HTMLDivElement,
+ MenuDotsDropdownProps
+>(({ overlay, iconOrientation = IconOrientation.Vertical, ...rest }, ref) => (
<AntdDropdown popupRender={() => overlay} {...rest}>
- <MenuDotsWrapper data-test="dropdown-trigger">
+ <MenuDotsWrapper ref={ref} tabIndex={0} data-test="dropdown-trigger">
{RenderIcon(iconOrientation)}
</MenuDotsWrapper>
</AntdDropdown>
-);
+));
+
+MenuDotsDropdown.displayName = 'MenuDotsDropdown';
export const NoAnimationDropdown = (props: NoAnimationDropdownProps) => {
const { children, onBlur, onKeyDown, ...rest } = props;
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx
index 686cf730b65..db5de015638 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx
@@ -245,7 +245,7 @@ test('does not dispatch a title change when dismissed with the close button', as
expect(store.getActions()).toEqual([]);
});
-test('returns focus to the tab header after the modal is cancelled', async () => {
+test('returns focus to the menu-dots trigger after the modal is cancelled', async () => {
openTabDropdown();
await waitFor(() =>
expect(screen.getByTestId('rename-tab-menu-option')).toBeInTheDocument(),
@@ -256,11 +256,11 @@ test('returns focus to the tab header after the modal is cancelled', async () =>
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
await waitFor(() =>
- expect(screen.getByTestId('sql-editor-tab-header')).toHaveFocus(),
+ expect(screen.getByTestId('dropdown-trigger')).toHaveFocus(),
);
});
-test('returns focus to the tab header after a successful rename', async () => {
+test('returns focus to the menu-dots trigger after a successful rename', async () => {
openTabDropdown();
await waitFor(() =>
expect(screen.getByTestId('rename-tab-menu-option')).toBeInTheDocument(),
@@ -272,7 +272,7 @@ test('returns focus to the tab header after a successful rename', async () => {
fireEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() =>
- expect(screen.getByTestId('sql-editor-tab-header')).toHaveFocus(),
+ expect(screen.getByTestId('dropdown-trigger')).toHaveFocus(),
);
});
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
index b6bb7553632..24cbf4485d0 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
@@ -113,7 +113,7 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
const [newTitle, setNewTitle] = useState('');
const renameInputRef = useRef<InputRef>(null);
- const tabHeaderRef = useRef<HTMLDivElement>(null);
+ const dropdownTriggerRef = useRef<HTMLDivElement>(null);
const trimmedTitle = newTitle.trim();
function openRenameModal() {
@@ -137,8 +137,8 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
}
setIsRenameModalOpen(false);
// Save closes via the show prop rather than the Modal's onHide, so return
- // focus to the tab header here, matching what openerRef does on dismiss.
- tabHeaderRef.current?.focus();
+ // focus to the dropdown trigger here, matching what openerRef does on dismiss.
+ dropdownTriggerRef.current?.focus();
}
const getStatusColor = (state: QueryState, theme: SupersetTheme): string => {
const statusColors: Record<QueryState, string> = {
@@ -156,12 +156,9 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
return statusColors[state] || theme.colorIcon;
};
return (
- <TabTitleWrapper
- ref={tabHeaderRef}
- tabIndex={-1}
- data-test="sql-editor-tab-header"
- >
+ <TabTitleWrapper data-test="sql-editor-tab-header">
<MenuDotsDropdown
+ ref={dropdownTriggerRef}
trigger={['click']}
overlay={
<Menu
@@ -256,7 +253,7 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
onHandledPrimaryAction={handleRenameTab}
primaryButtonName={t('Save')}
disablePrimaryButton={!trimmedTitle}
- openerRef={tabHeaderRef}
+ openerRef={dropdownTriggerRef}
>
<Input
ref={renameInputRef}