mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add keyboard navigation and selection to lists
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-sizer-container {
|
.auto-sizer-container {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import React, {
|
|||||||
RefObject,
|
RefObject,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
@@ -35,6 +36,7 @@ import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded
|
|||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
|
ItemListItem,
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
@@ -271,7 +273,9 @@ export const ItemGridList = ({
|
|||||||
const outerRef = useRef(null);
|
const outerRef = useRef(null);
|
||||||
const listRef = useRef<FixedSizeList<GridItemProps>>(null);
|
const listRef = useRef<FixedSizeList<GridItemProps>>(null);
|
||||||
const { ref: containerRef, width: containerWidth } = useElementSize();
|
const { ref: containerRef, width: containerWidth } = useElementSize();
|
||||||
const mergedContainerRef = useMergedRef(containerRef, rootRef);
|
const containerFocusRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const handleRef = useRef<ItemListHandle | null>(null);
|
||||||
|
const mergedContainerRef = useMergedRef(containerRef, rootRef, containerFocusRef);
|
||||||
|
|
||||||
const getDataFn = useCallback(() => {
|
const getDataFn = useCallback(() => {
|
||||||
return data;
|
return data;
|
||||||
@@ -332,11 +336,254 @@ export const ItemGridList = ({
|
|||||||
|
|
||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
|
// Scroll to a specific index
|
||||||
|
const scrollToIndex = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (!listRef.current || !tableMeta) return;
|
||||||
|
const row = Math.floor(index / tableMeta.columnCount);
|
||||||
|
listRef.current.scrollToItem(row, 'smart');
|
||||||
|
},
|
||||||
|
[tableMeta],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll to a specific offset
|
||||||
|
const scrollToOffset = useCallback((offset: number) => {
|
||||||
|
if (!listRef.current) return;
|
||||||
|
listRef.current.scrollTo(offset);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle keyboard navigation
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
console.log('handleKeyDown', e.key);
|
||||||
|
if (!enableSelection || !tableMeta) return;
|
||||||
|
if (
|
||||||
|
e.key !== 'ArrowDown' &&
|
||||||
|
e.key !== 'ArrowUp' &&
|
||||||
|
e.key !== 'ArrowLeft' &&
|
||||||
|
e.key !== 'ArrowRight'
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const selected = internalState.getSelected();
|
||||||
|
let currentIndex = -1;
|
||||||
|
|
||||||
|
if (selected.length > 0) {
|
||||||
|
const lastSelected = selected[selected.length - 1];
|
||||||
|
currentIndex = data.findIndex(
|
||||||
|
(d: any) => d && typeof d === 'object' && 'id' in d && d.id === lastSelected.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate grid position
|
||||||
|
const currentRow =
|
||||||
|
currentIndex !== -1 ? Math.floor(currentIndex / tableMeta.columnCount) : 0;
|
||||||
|
const currentCol = currentIndex !== -1 ? currentIndex % tableMeta.columnCount : 0;
|
||||||
|
const totalRows = Math.ceil(data.length / tableMeta.columnCount);
|
||||||
|
|
||||||
|
let newIndex = 0;
|
||||||
|
if (currentIndex !== -1) {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowDown': {
|
||||||
|
// Move down one row
|
||||||
|
const nextRow = currentRow + 1;
|
||||||
|
if (nextRow < totalRows) {
|
||||||
|
const nextRowStart = nextRow * tableMeta.columnCount;
|
||||||
|
const nextRowEnd = Math.min(
|
||||||
|
nextRowStart + tableMeta.columnCount - 1,
|
||||||
|
data.length - 1,
|
||||||
|
);
|
||||||
|
// Keep same column position, or use last item in row if column doesn't exist
|
||||||
|
newIndex = Math.min(nextRowStart + currentCol, nextRowEnd);
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowLeft': {
|
||||||
|
// Move left, wrap to previous row if at start of row
|
||||||
|
if (currentCol > 0) {
|
||||||
|
newIndex = currentIndex - 1;
|
||||||
|
} else if (currentRow > 0) {
|
||||||
|
// Wrap to end of previous row
|
||||||
|
newIndex = Math.max(
|
||||||
|
(currentRow - 1) * tableMeta.columnCount +
|
||||||
|
tableMeta.columnCount -
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
newIndex = Math.min(newIndex, data.length - 1);
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowRight': {
|
||||||
|
// Move right, wrap to next row if at end of row
|
||||||
|
if (
|
||||||
|
currentCol < tableMeta.columnCount - 1 &&
|
||||||
|
currentIndex < data.length - 1
|
||||||
|
) {
|
||||||
|
newIndex = currentIndex + 1;
|
||||||
|
} else if (currentRow < totalRows - 1) {
|
||||||
|
// Wrap to start of next row
|
||||||
|
newIndex = Math.min(
|
||||||
|
(currentRow + 1) * tableMeta.columnCount,
|
||||||
|
data.length - 1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowUp': {
|
||||||
|
// Move up one row
|
||||||
|
const prevRow = currentRow - 1;
|
||||||
|
if (prevRow >= 0) {
|
||||||
|
const prevRowStart = prevRow * tableMeta.columnCount;
|
||||||
|
const prevRowEnd = Math.min(
|
||||||
|
prevRowStart + tableMeta.columnCount - 1,
|
||||||
|
data.length - 1,
|
||||||
|
);
|
||||||
|
// Keep same column position, or use last item in row if column doesn't exist
|
||||||
|
newIndex = Math.min(prevRowStart + currentCol, prevRowEnd);
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No selection, start at first item
|
||||||
|
newIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem: any = data[newIndex];
|
||||||
|
if (!newItem) return;
|
||||||
|
|
||||||
|
// Handle Shift + Arrow for incremental range selection (matches shift+click behavior)
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const selectedItems = internalState.getSelected();
|
||||||
|
const lastSelectedItem = selectedItems[selectedItems.length - 1];
|
||||||
|
|
||||||
|
if (lastSelectedItem) {
|
||||||
|
// Find the indices of the last selected item and new item
|
||||||
|
const lastIndex = data.findIndex(
|
||||||
|
(d: any) =>
|
||||||
|
d && typeof d === 'object' && 'id' in d && d.id === lastSelectedItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastIndex !== -1 && newIndex !== -1) {
|
||||||
|
// Create range selection from last selected to new position
|
||||||
|
const startIndex = Math.min(lastIndex, newIndex);
|
||||||
|
const stopIndex = Math.max(lastIndex, newIndex);
|
||||||
|
|
||||||
|
const rangeItems: ItemListItem[] = [];
|
||||||
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
|
const rangeItem = data[i];
|
||||||
|
if (
|
||||||
|
rangeItem &&
|
||||||
|
typeof rangeItem === 'object' &&
|
||||||
|
'id' in rangeItem &&
|
||||||
|
'serverId' in rangeItem
|
||||||
|
) {
|
||||||
|
rangeItems.push({
|
||||||
|
_serverId: (rangeItem as any).serverId,
|
||||||
|
id: (rangeItem as any).id,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add range items to selection (matching shift+click behavior)
|
||||||
|
const currentSelected = internalState.getSelected();
|
||||||
|
const newSelected = [...currentSelected];
|
||||||
|
rangeItems.forEach((rangeItem) => {
|
||||||
|
if (!newSelected.some((selected) => selected.id === rangeItem.id)) {
|
||||||
|
newSelected.push(rangeItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure the last item in selection is the item at newIndex for incremental extension
|
||||||
|
const newItemListItem: ItemListItem = {
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
};
|
||||||
|
// Remove the new item from its current position if it exists
|
||||||
|
const filteredSelected = newSelected.filter(
|
||||||
|
(item) => item.id !== newItemListItem.id,
|
||||||
|
);
|
||||||
|
// Add it at the end so it becomes the last selected item
|
||||||
|
filteredSelected.push(newItemListItem);
|
||||||
|
internalState.setSelected(filteredSelected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No previous selection, just select the new item
|
||||||
|
internalState.setSelected([
|
||||||
|
{
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Without Shift: select only the new item
|
||||||
|
internalState.setSelected([
|
||||||
|
{
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToIndex(newIndex);
|
||||||
|
},
|
||||||
|
[data, enableSelection, internalState, itemType, tableMeta, scrollToIndex],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create imperative handle
|
||||||
|
const imperativeHandle: ItemListHandle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
clearExpanded: () => {
|
||||||
|
internalState.clearExpanded();
|
||||||
|
},
|
||||||
|
clearSelected: () => {
|
||||||
|
internalState.clearSelected();
|
||||||
|
},
|
||||||
|
getItem: (index: number) => data[index],
|
||||||
|
getItemCount: () => data.length,
|
||||||
|
getItems: () => data,
|
||||||
|
internalState,
|
||||||
|
scrollToIndex: (index: number) => {
|
||||||
|
scrollToIndex(index);
|
||||||
|
},
|
||||||
|
scrollToOffset: (offset: number) => {
|
||||||
|
scrollToOffset(offset);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [data, internalState, scrollToIndex, scrollToOffset]);
|
||||||
|
|
||||||
|
// Expose handle via ref
|
||||||
|
useEffect(() => {
|
||||||
|
handleRef.current = imperativeHandle;
|
||||||
|
}, [imperativeHandle]);
|
||||||
|
|
||||||
|
// Expose handle via forwardRef
|
||||||
|
useImperativeHandle(ref, () => imperativeHandle, [imperativeHandle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.itemGridContainer}
|
className={styles.itemGridContainer}
|
||||||
data-overlayscrollbars-initialize=""
|
data-overlayscrollbars-initialize=""
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onMouseDown={(e) => (e.currentTarget as HTMLDivElement).focus()}
|
||||||
ref={mergedContainerRef}
|
ref={mergedContainerRef}
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<VirtualizedGridList
|
<VirtualizedGridList
|
||||||
controls={controls}
|
controls={controls}
|
||||||
|
|||||||
@@ -556,6 +556,7 @@ export const ItemTableList = ({
|
|||||||
const [showLeftShadow, setShowLeftShadow] = useState(false);
|
const [showLeftShadow, setShowLeftShadow] = useState(false);
|
||||||
const [showRightShadow, setShowRightShadow] = useState(false);
|
const [showRightShadow, setShowRightShadow] = useState(false);
|
||||||
const handleRef = useRef<ItemListHandle | null>(null);
|
const handleRef = useRef<ItemListHandle | null>(null);
|
||||||
|
const containerFocusRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const onScrollEndRef = useRef<ItemTableListProps['onScrollEnd']>(onScrollEnd);
|
const onScrollEndRef = useRef<ItemTableListProps['onScrollEnd']>(onScrollEnd);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -582,17 +583,61 @@ export const ItemTableList = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const DEFAULT_ROW_HEIGHT = size === 'compact' ? 40 : size === 'large' ? 88 : 64;
|
||||||
|
|
||||||
const calculateScrollTopForIndex = useCallback(
|
const calculateScrollTopForIndex = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
|
const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
|
||||||
let scrollTop = 0;
|
let scrollTop = 0;
|
||||||
|
|
||||||
|
// Create a minimal mock cellProps for rowHeight function calls if needed
|
||||||
|
const mockCellProps: TableItemProps = {
|
||||||
|
cellPadding,
|
||||||
|
columns: parsedColumns,
|
||||||
|
controls: {} as ItemControls,
|
||||||
|
data: enableHeader ? [null, ...data] : data,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getRowHeight: () => DEFAULT_ROW_HEIGHT,
|
||||||
|
internalState: {} as ItemListStateActions,
|
||||||
|
itemType,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < adjustedIndex; i++) {
|
for (let i = 0; i < adjustedIndex; i++) {
|
||||||
const height = rowHeight as number;
|
let height: number;
|
||||||
|
if (typeof rowHeight === 'number') {
|
||||||
|
height = rowHeight;
|
||||||
|
} else if (typeof rowHeight === 'function') {
|
||||||
|
height = rowHeight(i, mockCellProps);
|
||||||
|
} else {
|
||||||
|
height = DEFAULT_ROW_HEIGHT;
|
||||||
|
}
|
||||||
scrollTop += height;
|
scrollTop += height;
|
||||||
}
|
}
|
||||||
return scrollTop;
|
return scrollTop;
|
||||||
},
|
},
|
||||||
[enableHeader, rowHeight],
|
[
|
||||||
|
enableHeader,
|
||||||
|
rowHeight,
|
||||||
|
size,
|
||||||
|
DEFAULT_ROW_HEIGHT,
|
||||||
|
cellPadding,
|
||||||
|
parsedColumns,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
itemType,
|
||||||
|
data,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToTableIndex = useCallback(
|
const scrollToTableIndex = useCallback(
|
||||||
@@ -1038,6 +1083,125 @@ export const ItemTableList = ({
|
|||||||
[data, enableSelection, internalState, itemType],
|
[data, enableSelection, internalState, itemType],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (!enableSelection) return;
|
||||||
|
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const selected = internalState.getSelected();
|
||||||
|
let currentIndex = -1;
|
||||||
|
|
||||||
|
if (selected.length > 0) {
|
||||||
|
const lastSelected = selected[selected.length - 1];
|
||||||
|
currentIndex = data.findIndex(
|
||||||
|
(d: any) => d && typeof d === 'object' && 'id' in d && d.id === lastSelected.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let newIndex = 0;
|
||||||
|
if (currentIndex !== -1) {
|
||||||
|
newIndex =
|
||||||
|
e.key === 'ArrowDown'
|
||||||
|
? Math.min(currentIndex + 1, data.length - 1)
|
||||||
|
: Math.max(currentIndex - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem: any = data[newIndex];
|
||||||
|
if (!newItem) return;
|
||||||
|
|
||||||
|
// Handle Shift + Arrow for incremental range selection (matches shift+click behavior)
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const selectedItems = internalState.getSelected();
|
||||||
|
const lastSelectedItem = selectedItems[selectedItems.length - 1];
|
||||||
|
|
||||||
|
if (lastSelectedItem) {
|
||||||
|
// Find the indices of the last selected item and new item
|
||||||
|
const lastIndex = data.findIndex(
|
||||||
|
(d: any) =>
|
||||||
|
d && typeof d === 'object' && 'id' in d && d.id === lastSelectedItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastIndex !== -1 && newIndex !== -1) {
|
||||||
|
// Create range selection from last selected to new position
|
||||||
|
const startIndex = Math.min(lastIndex, newIndex);
|
||||||
|
const stopIndex = Math.max(lastIndex, newIndex);
|
||||||
|
|
||||||
|
const rangeItems: ItemListItem[] = [];
|
||||||
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
|
const rangeItem = data[i];
|
||||||
|
if (
|
||||||
|
rangeItem &&
|
||||||
|
typeof rangeItem === 'object' &&
|
||||||
|
'id' in rangeItem &&
|
||||||
|
'serverId' in rangeItem
|
||||||
|
) {
|
||||||
|
rangeItems.push({
|
||||||
|
_serverId: (rangeItem as any).serverId,
|
||||||
|
id: (rangeItem as any).id,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add range items to selection (matching shift+click behavior)
|
||||||
|
const currentSelected = internalState.getSelected();
|
||||||
|
const newSelected = [...currentSelected];
|
||||||
|
rangeItems.forEach((rangeItem) => {
|
||||||
|
if (!newSelected.some((selected) => selected.id === rangeItem.id)) {
|
||||||
|
newSelected.push(rangeItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure the last item in selection is the item at newIndex for incremental extension
|
||||||
|
const newItemListItem: ItemListItem = {
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
};
|
||||||
|
// Remove the new item from its current position if it exists
|
||||||
|
const filteredSelected = newSelected.filter(
|
||||||
|
(item) => item.id !== newItemListItem.id,
|
||||||
|
);
|
||||||
|
// Add it at the end so it becomes the last selected item
|
||||||
|
filteredSelected.push(newItemListItem);
|
||||||
|
internalState.setSelected(filteredSelected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No previous selection, just select the new item
|
||||||
|
internalState.setSelected([
|
||||||
|
{
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Without Shift: select only the new item
|
||||||
|
internalState.setSelected([
|
||||||
|
{
|
||||||
|
_serverId: newItem.serverId,
|
||||||
|
id: newItem.id,
|
||||||
|
itemType,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = calculateScrollTopForIndex(newIndex);
|
||||||
|
scrollToTableOffset(offset);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
data,
|
||||||
|
enableSelection,
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
calculateScrollTopForIndex,
|
||||||
|
scrollToTableOffset,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const isInitialScrollPositionSet = useRef<boolean>(false);
|
const isInitialScrollPositionSet = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1088,7 +1252,13 @@ export const ItemTableList = ({
|
|||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemTableListContainer}>
|
<div
|
||||||
|
className={styles.itemTableListContainer}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onMouseDown={(e) => (e.currentTarget as HTMLDivElement).focus()}
|
||||||
|
ref={containerFocusRef}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
<VirtualizedTableGrid
|
<VirtualizedTableGrid
|
||||||
calculatedColumnWidths={calculatedColumnWidths}
|
calculatedColumnWidths={calculatedColumnWidths}
|
||||||
CellComponent={CellComponent}
|
CellComponent={CellComponent}
|
||||||
|
|||||||
Reference in New Issue
Block a user