Skip to content

Commit

Permalink
fix(EscapeKey): global escape key management closes1872 (#2478)
Browse files Browse the repository at this point in the history
* fix(EscapeKey): global escape key management closes1872

* fix(EscapeKey): global escape key management closes1872

* fix(EscapeKey): global escape key management closes1872

* fix(EscapeKey): global escape key management closes1872

* fix(datatable): make data table content tab accessible closes1872

* fix(datatable): make data table content tab accessible closes1872

* fix(datatable): make data table content tab accessible closes1872

* fix(geolocator): fix geolocator container width closes2493

* fix(geolocator): fix geolocator container width closes2493

* fix(geolocator): fix geolocator container width closes2493
  • Loading branch information
kaminderpal authored Sep 27, 2024
1 parent 6ac8c8d commit f662569
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export const helpOpenClosePanelByIdState = (
buttonId: string,
groupName: string | undefined,
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>,
status: boolean
status: boolean,
isFocusTrapped: boolean = false
): void => {
// Read the group name
const theGroupName = groupName || helpFindGroupName(buttonPanelGroups, buttonId);
Expand All @@ -32,7 +33,13 @@ export const helpOpenClosePanelByIdState = (
panelGroups[buttonPanelGroupName] = Object.entries(buttonPanelGroup).reduce((acc, [buttonGroupName, buttonGroup]) => {
acc[buttonGroupName] = {
...buttonGroup,
...(buttonGroup.panel && { panel: { ...buttonGroup.panel, status: buttonGroupName === buttonId ? status : false } }),
...(buttonGroup.panel && {
panel: {
...buttonGroup.panel,
status: buttonGroupName === buttonId ? status : false,
isFocusTrapped: buttonGroupName === buttonId ? isFocusTrapped : false,
},
}),
};

return acc;
Expand All @@ -47,13 +54,14 @@ export const helpOpenPanelById = (
buttonPanelGroups: ButtonPanelGroupType,
buttonId: string,
groupName: string | undefined,
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>,
isFocusTrapped?: boolean
): void => {
// Read the group name
const theGroupName = groupName || helpFindGroupName(buttonPanelGroups, buttonId);

// Open the panel
helpOpenClosePanelByIdState(buttonPanelGroups, buttonId, theGroupName, setterCallback, true);
helpOpenClosePanelByIdState(buttonPanelGroups, buttonId, theGroupName, setterCallback, true, isFocusTrapped);
};

export const helpClosePanelById = (
Expand Down
14 changes: 10 additions & 4 deletions packages/geoview-core/src/core/components/app-bar/app-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import { useState, useRef, useEffect, useCallback, Fragment, useMemo, ReactNode } from 'react';
import { useState, useRef, useEffect, useCallback, Fragment, useMemo, ReactNode, KeyboardEvent } from 'react';
import { capitalize, camelCase } from 'lodash';
import { useTheme } from '@mui/material/styles';
import {
Expand Down Expand Up @@ -51,6 +51,7 @@ import { AbstractPlugin } from '@/api/plugin/abstract-plugin';
import { CV_DEFAULT_APPBAR_CORE, CV_DEFAULT_APPBAR_TABS_ORDER } from '@/api/config/types/config-constants';
import { CONTAINER_TYPE } from '@/core/utils/constant';
import { TypeValidAppBarCoreProps } from '@/api/config/types/map-schema-types';
import { handleEscapeKey } from '@/core/utils/utilities';

interface GroupPanelType {
icon: ReactNode;
Expand Down Expand Up @@ -92,7 +93,7 @@ export function AppBar(props: AppBarProps): JSX.Element {
const activeModalId = useUIActiveFocusItem().activeElementId;
const interaction = useMapInteraction();
const appBarComponents = useUIAppbarComponents();
const { tabId, tabGroup, isOpen } = useActiveAppBarTab();
const { tabId, tabGroup, isOpen, isFocusTrapped } = useActiveAppBarTab();
const { hideClickMarker } = useMapStoreActions();

const isMapFullScreen = useAppFullscreenActive();
Expand Down Expand Up @@ -151,9 +152,9 @@ export function AppBar(props: AppBarProps): JSX.Element {
// Log
logger.logTraceUseCallback('APP-BAR - openPanelById', buttonId);
// Redirect to helper
helpOpenPanelById(buttonPanelGroups, buttonId, groupName, setButtonPanelGroups);
helpOpenPanelById(buttonPanelGroups, buttonId, groupName, setButtonPanelGroups, isFocusTrapped);
},
[buttonPanelGroups]
[buttonPanelGroups, isFocusTrapped]
);

const handleButtonClicked = useCallback(
Expand Down Expand Up @@ -439,6 +440,11 @@ export function AppBar(props: AppBarProps): JSX.Element {
button={buttonPanel.button}
onPanelOpened={buttonPanel.onPanelOpened}
onPanelClosed={hideClickMarker}
handleKeyDown={(e: KeyboardEvent) =>
handleEscapeKey(e.key, tabId, isFocusTrapped, () => {
handleGeneralCloseClicked(buttonPanel.button?.id ?? '', buttonPanel?.groupName ?? '');
})
}
onGeneralCloseClicked={() => handleGeneralCloseClicked(buttonPanel.button?.id ?? '', buttonPanel?.groupName ?? '')}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GeoCaIcon, IconButton, Popper } from '@/ui';
import { useGeoViewMapId } from '@/core/stores/geoview-store';
import { useMapInteraction } from '@/core/stores/store-interface-and-intial-values/map-state';
import { GitHubIcon } from '@/ui/icons';
import { handleEscapeKey } from '@/core/utils/utilities';

// eslint-disable-next-line no-underscore-dangle
declare const __VERSION__: TypeAppVersion;
Expand All @@ -30,6 +31,7 @@ export default function Version(): JSX.Element {

const mapId = useGeoViewMapId();
const interaction = useMapInteraction();

const mapElem = document.getElementById(`shell-${mapId}`);

const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
Expand Down Expand Up @@ -92,7 +94,14 @@ export default function Version(): JSX.Element {
</SvgIcon>
</IconButton>

<Popper open={open} anchorEl={anchorEl} placement="right-end" onClose={handleClickAway} container={mapElem}>
<Popper
open={open}
anchorEl={anchorEl}
placement="right-end"
onClose={handleClickAway}
container={mapElem}
handleKeyDown={(key, callBackFn) => handleEscapeKey(key, '', false, callBackFn)}
>
<Paper sx={sxClasses.versionInfoPanel}>
<Typography sx={sxClasses.versionsInfoTitle} component="h3">
{t('appbar.version')}
Expand Down
6 changes: 3 additions & 3 deletions packages/geoview-core/src/core/components/common/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function Layout({
const responsiveLayoutRef = useRef<ResponsiveGridLayoutExposedMethods>(null);
const theme = useTheme();

const { setSelectedFooterLayerListItem } = useUIStoreActions();
const { setSelectedFooterLayerListItemId } = useUIStoreActions();
/**
* Handles clicks to layers in left panel. Sets selected layer.
*
Expand All @@ -47,9 +47,9 @@ export function Layout({
responsiveLayoutRef.current?.setIsRightPanelVisible(true);
responsiveLayoutRef.current?.setRightPanelFocus();
// set the focus item when layer item clicked.
setSelectedFooterLayerListItem(`${layer.layerUniqueId}`);
setSelectedFooterLayerListItemId(`${layer.layerUniqueId}`);
},
[onLayerListClicked, setSelectedFooterLayerListItem]
[onLayerListClicked, setSelectedFooterLayerListItemId]
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import FullScreenDialog from './full-screen-dialog';
import { logger } from '@/core/utils/logger';
import { ArrowBackIcon, ArrowForwardIcon, CloseIcon, QuestionMarkIcon } from '@/ui/icons';
import { useAppGuide, useAppFullscreenActive } from '@/core/stores/store-interface-and-intial-values/app-state';
import { useUISelectedFooterLayerListItem } from '@/core/stores/store-interface-and-intial-values/ui-state';
import { useUISelectedFooterLayerListItemId } from '@/core/stores/store-interface-and-intial-values/ui-state';
import { TypeContainerBox } from '@/core/types/global-types';
import { CONTAINER_TYPE } from '@/core/utils/constant';
import { handleEscapeKey } from '@/core/utils/utilities';

interface ResponsiveGridLayoutProps {
leftTop?: ReactNode;
Expand Down Expand Up @@ -53,7 +54,7 @@ const ResponsiveGridLayout = forwardRef(
const { t } = useTranslation<string>();
const guide = useAppGuide();
const isMapFullScreen = useAppFullscreenActive();
const selectedFooterLayerListItem = useUISelectedFooterLayerListItem();
const selectedFooterLayerListItemId = useUISelectedFooterLayerListItemId();

const [isRightPanelVisible, setIsRightPanelVisible] = useState(false);
const [isGuideOpen, setIsGuideOpen] = useState(false);
Expand Down Expand Up @@ -98,22 +99,27 @@ const ResponsiveGridLayout = forwardRef(
}
}, [hideEnlargeBtn, isEnlarged]);

// Callback to be executed after escape key is pressed.
const handleEscapeKeyCallback = useCallback((): void => {
if (rightMainRef.current && selectedFooterLayerListItemId.length) {
rightMainRef.current.tabIndex = -1;
}
}, [selectedFooterLayerListItemId]);

const handleKeyDown = useCallback(
(event: KeyboardEvent): void => handleEscapeKey(event.key, selectedFooterLayerListItemId, true, handleEscapeKeyCallback),
[handleEscapeKeyCallback, selectedFooterLayerListItemId]
);

// return back the focus to layeritem for which right panel was opened.
useEffect(() => {
const handleEscapeKey = (event: KeyboardEvent): void => {
if (event.key === 'Escape' && selectedFooterLayerListItem.length && rightMainRef.current) {
rightMainRef.current.tabIndex = -1;
document.getElementById(selectedFooterLayerListItem)?.focus();
}
};

const rightPanel = rightMainRef.current;
rightPanel?.addEventListener('keydown', handleEscapeKey);
rightPanel?.addEventListener('keydown', handleKeyDown);

return () => {
rightPanel?.removeEventListener('keydown', handleEscapeKey);
rightPanel?.removeEventListener('keydown', handleKeyDown);
};
}, [selectedFooterLayerListItem]);
}, [handleKeyDown]);

/**
* Handles click on the Enlarge button.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
type MRT_RowVirtualizer as MRTRowVirtualizer,
type MRT_ColumnFiltersState as MRTColumnFiltersState,
type MRT_DensityState as MRTDensityState,
type MRT_ColumnVirtualizer as MRTColumnVirtualizer,
Box,
Button,
IconButton,
Expand Down Expand Up @@ -72,6 +73,7 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
// internal state
const [density, setDensity] = useState<MRTDensityState>('compact');
const rowVirtualizerInstanceRef = useRef<MRTRowVirtualizer>(null);
const columnVirtualizerInstanceRef = useRef<MRTColumnVirtualizer>(null);
const [sorting, setSorting] = useState<MRTSortingState>([]);

// get store actions and values
Expand Down Expand Up @@ -139,8 +141,13 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
</Button>
);
}

// convert string to react component.
return typeof cellValue === 'string' ? <HtmlToReact htmlContent={cellValue} /> : cellValue;
return (typeof cellValue === 'string' && cellValue.length) || typeof cellValue === 'number' ? (
<HtmlToReact htmlContent={cellValue.toString()} itemOptions={{ tabIndex: 0 }} />
) : (
cellValue
);
},
[initLightBox, t]
);
Expand Down Expand Up @@ -183,7 +190,7 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
const formattedDate = DateMgt.formatDate(date, 'YYYY-MM-DDThh:mm:ss');
return (
<Tooltip title={formattedDate} arrow>
<Box>{formattedDate}</Box>
<Box tabIndex={0}>{formattedDate}</Box>
</Tooltip>
);
}, []);
Expand Down Expand Up @@ -431,6 +438,7 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
enableRowVirtualization: true,
muiTableContainerProps: { sx: { maxHeight: tableHeight } },
rowVirtualizerInstanceRef,
columnVirtualizerInstanceRef,
rowVirtualizerOptions: { overscan: 5 },
columnVirtualizerOptions: { overscan: 2 },
localization: dataTableLocalization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ export function FooterBar(props: FooterBarProps): JSX.Element | null {
// inject guide tab at last position of tabs.
return Object.keys({ ...tabsList, ...{ guide: {} } }).map((tab, index) => {
return {
id: tab,
id: `${mapId}-${tab}${index}`,
value: index,
label: `${camelCase(tab)}.title`,
icon: allTabs[tab]?.icon ?? '',
content: allTabs[tab]?.content ?? '',
} as TypeTabs;
});
}, [memoTabs, tabsList]);
}, [memoTabs, tabsList, mapId]);

/**
* Calculate resize values from popover values defined in store.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { logger } from '@/core/utils/logger';
import { CV_DEFAULT_APPBAR_CORE } from '@/api/config/types/config-constants';
import { FocusTrapContainer } from '@/core/components/common';
import { useGeoViewMapId } from '@/core/stores/geoview-store';
import { handleEscapeKey } from '@/core/utils/utilities';

export interface GeoListItem {
key: string;
Expand Down Expand Up @@ -46,8 +47,10 @@ export function Geolocator(): JSX.Element {
const { tabGroup, isOpen } = useActiveAppBarTab();

const urlRef = useRef<string>(`${geolocatorServiceURL}&lang=${displayLanguage}`);
const geolocatorRef = useRef<HTMLDivElement>();
const abortControllerRef = useRef<AbortController | null>(null);
const fetchTimerRef = useRef<NodeJS.Timeout | undefined>();
const searchInputRef = useRef<HTMLInputElement>();
const MIN_SEARCH_LENGTH = 3;

/**
Expand Down Expand Up @@ -203,6 +206,16 @@ export function Geolocator(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchValue]);

useEffect(() => {
// Log
logger.logTraceUseEffect('GEOLOCATOR - mount');

const handleGeolocatorEscapeKey = (e: KeyboardEvent): void => {
handleEscapeKey(e.key, '', false, () => resetSearch());
};
geolocatorRef.current?.addEventListener('keydown', handleGeolocatorEscapeKey);
}, [mapId, resetSearch]);

useEffect(() => {
return () => {
// Cleanup function to abort any pending requests
Expand All @@ -213,6 +226,13 @@ export function Geolocator(): JSX.Element {
};
}, []);

useEffect(() => {
// Set the focus on search field when geolocator is opened.
if (isOpen && tabGroup === CV_DEFAULT_APPBAR_CORE.GEOLOCATOR && searchInputRef.current) {
searchInputRef.current.querySelector('input')?.focus();
}
}, [isOpen, tabGroup]);

/**
* Effect that will track fetch call, so that after 15 seconds if no response comes back,
* Error will be displayed.
Expand All @@ -236,6 +256,7 @@ export function Geolocator(): JSX.Element {
visibility={tabGroup === CV_DEFAULT_APPBAR_CORE.GEOLOCATOR && isOpen ? 'visible' : 'hidden'}
id="geolocator-search"
tabIndex={tabGroup === CV_DEFAULT_APPBAR_CORE.GEOLOCATOR && isOpen ? 0 : -1}
ref={geolocatorRef}
>
<Box sx={sxClasses.geolocator}>
<AppBarUI position="static">
Expand All @@ -249,7 +270,13 @@ export function Geolocator(): JSX.Element {
}
}}
>
<StyledInputField placeholder={t('geolocator.search')!} autoFocus onChange={onChange} value={searchValue} />
<StyledInputField
placeholder={t('geolocator.search')!}
autoFocus
onChange={onChange}
value={searchValue}
ref={searchInputRef}
/>
<Box sx={{ display: 'flex', marginLeft: 'auto', alignItems: 'center' }}>
<IconButton
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ export function LayersPanel({ containerType }: TypeLayersPanel): JSX.Element {
const [isLayoutEnlarged, setIsLayoutEnlarged] = useState<boolean>(false);

const { setSelectedLayerPath } = useLayerStoreActions();
const { setSelectedFooterLayerListItem } = useUIStoreActions();
const { setSelectedFooterLayerListItemId } = useUIStoreActions();

const responsiveLayoutRef = useRef<ResponsiveGridLayoutExposedMethods>(null);

const showLayerDetailsPanel = (layer: TypeLegendLayer): void => {
responsiveLayoutRef.current?.setIsRightPanelVisible(true);
responsiveLayoutRef.current?.setRightPanelFocus();
// set the focus item when layer item clicked.
setSelectedFooterLayerListItem(`${layer.layerId}`);
setSelectedFooterLayerListItemId(`${layer.layerId}`);
};

const leftPanel = (): JSX.Element => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function DeleteUndoButton(props: DeleteUndoButtonProps): JSX.Element {
// get store actions
const { deleteLayer, setLayerDeleteInProgress, getLayerDeleteInProgress } = useLayerStoreActions();
const { getVisibilityFromOrderedLayerInfo, setOrToggleLayerVisibility } = useMapStoreActions();
const { setSelectedFooterLayerListItem } = useUIStoreActions();
const { setSelectedFooterLayerListItemId } = useUIStoreActions();

const handleDeleteClick = (): void => {
if (getVisibilityFromOrderedLayerInfo(layer.layerPath)) setOrToggleLayerVisibility(layer.layerPath);
Expand All @@ -74,14 +74,14 @@ export function DeleteUndoButton(props: DeleteUndoButtonProps): JSX.Element {
if (e.key === 'Enter') {
e.preventDefault();
handleDeleteClick();
setSelectedFooterLayerListItem(layer.layerId);
setSelectedFooterLayerListItemId(layer.layerId);
}
};

const handleUndoDeleteKeyDown = (e: KeyboardEvent): void => {
if (e.key === 'Enter') {
handleUndoClick();
setSelectedFooterLayerListItem('');
setSelectedFooterLayerListItemId('');
e.preventDefault();
}
};
Expand Down
Loading

0 comments on commit f662569

Please sign in to comment.