[Keyboard Navigation - Azure Cosmos DB - Data Explorer]: Keyboard focus is not retaining back to 'more' button after closing 'Delete container' dialog. (#1978)
* [accessibility-2262594]: [Keyboard Navigation - Azure Cosmos DB - Data Explorer]: Keyboard focus is not retaining back to 'more' button after closing 'Delete container' dialog. * Optimize closeSidePanel: add timeout cleanup to prevent memory leaks and ensure proper focus behavior --------- Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
This commit is contained in:
parent
5a2f78b51e
commit
aa88815c6e
|
@ -56,13 +56,15 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
|
||||
items.push({
|
||||
iconSrc: DeleteDatabaseIcon,
|
||||
onClick: () =>
|
||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||
(useSidePanel.getState().getRef = lastFocusedElement),
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Delete " + getDatabaseName(),
|
||||
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />,
|
||||
),
|
||||
);
|
||||
},
|
||||
label: `Delete ${getDatabaseName()}`,
|
||||
styleClass: "deleteDatabaseMenuItem",
|
||||
});
|
||||
|
@ -146,8 +148,9 @@ export const createCollectionContextMenuButton = (
|
|||
if (configContext.platform !== Platform.Fabric) {
|
||||
items.push({
|
||||
iconSrc: DeleteCollectionIcon,
|
||||
onClick: () => {
|
||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||
useSelectedNode.getState().setSelectedNode(selectedCollection);
|
||||
(useSidePanel.getState().getRef = lastFocusedElement),
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
|
|
|
@ -23,7 +23,7 @@ import { useCallback } from "react";
|
|||
|
||||
export interface TreeNodeMenuItem {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
onClick: (value?: React.RefObject<HTMLElement>) => void;
|
||||
iconSrc?: string;
|
||||
isDisabled?: boolean;
|
||||
styleClass?: string;
|
||||
|
@ -74,6 +74,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||
openItems,
|
||||
}: TreeNodeComponentProps): JSX.Element => {
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
const contextMenuRef = React.useRef<HTMLButtonElement>(null);
|
||||
const treeStyles = useTreeStyles();
|
||||
|
||||
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
|
||||
|
@ -141,7 +142,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
||||
disabled={menuItem.isDisabled}
|
||||
key={menuItem.label}
|
||||
onClick={menuItem.onClick}
|
||||
onClick={() => menuItem.onClick(contextMenuRef)}
|
||||
>
|
||||
{menuItem.label}
|
||||
</MenuItem>
|
||||
|
@ -190,6 +191,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
|
||||
data-test="TreeNode/ContextMenuTrigger"
|
||||
appearance="subtle"
|
||||
ref={contextMenuRef}
|
||||
icon={<MoreHorizontal20Regular />}
|
||||
/>
|
||||
</MenuTrigger>
|
||||
|
|
|
@ -1478,14 +1478,14 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||
<MenuList>
|
||||
<MenuItem
|
||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
||||
onClick={[MockFunction enabledItemClick]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
enabledItem
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
||||
disabled={true}
|
||||
onClick={[MockFunction disabledItemClick]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
disabledItem
|
||||
</MenuItem>
|
||||
|
@ -1518,7 +1518,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||
<MenuItem
|
||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
||||
key="enabledItem"
|
||||
onClick={[MockFunction enabledItemClick]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
enabledItem
|
||||
</MenuItem>
|
||||
|
@ -1526,7 +1526,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
||||
disabled={true}
|
||||
key="disabledItem"
|
||||
onClick={[MockFunction disabledItemClick]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
disabledItem
|
||||
</MenuItem>
|
||||
|
|
|
@ -7,12 +7,20 @@ export interface SidePanelState {
|
|||
headerText?: string;
|
||||
openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void;
|
||||
closeSidePanel: () => void;
|
||||
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
|
||||
}
|
||||
|
||||
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
|
||||
isOpen: false,
|
||||
panelWidth: "440px",
|
||||
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
||||
set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })),
|
||||
closeSidePanel: () => set((state) => ({ ...state, isOpen: false })),
|
||||
closeSidePanel: () => {
|
||||
const lastFocusedElement = useSidePanel.getState().getRef;
|
||||
set((state) => ({ ...state, isOpen: false }));
|
||||
const timeoutId = setTimeout(() => {
|
||||
lastFocusedElement?.current?.focus();
|
||||
set({ getRef: undefined });
|
||||
}, 300);
|
||||
return () => clearTimeout(timeoutId);
|
||||
},
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue