diff --git a/src/renderer/components/item-list/item-table-list/columns/row-index-column.tsx b/src/renderer/components/item-list/item-table-list/columns/row-index-column.tsx
index 40832ad5e..6780abb5d 100644
--- a/src/renderer/components/item-list/item-table-list/columns/row-index-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/columns/row-index-column.tsx
@@ -4,5 +4,5 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const RowIndexColumn = (props: ItemTableListInnerColumn) => {
- return {props.rowIndex + 1};
+ return {props.rowIndex};
};
diff --git a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
index c7f0fa769..a72eb1a32 100644
--- a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
@@ -27,6 +27,7 @@ import { CellProps } from '/@/renderer/components/item-list/item-table-list/item
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
import { TableColumn } from '/@/shared/types/types';
+import { createDoubleClickHandler } from '/@/shared/utils/double-click-handler';
export interface ItemTableListColumn extends CellComponentProps {}
@@ -116,6 +117,7 @@ export const TableColumnTextContainer = (
) => {
const containerRef = useRef(null);
const isDataRow = props.enableHeader && props.rowIndex > 0;
+ const dataIndex = isDataRow ? props.rowIndex - 1 : props.rowIndex;
useEffect(() => {
if (!isDataRow || !containerRef.current || !props.enableRowHover) return;
@@ -157,7 +159,18 @@ export const TableColumnTextContainer = (
props.enableRowBorders && props.enableHeader && props.rowIndex > 0,
})}
data-row-index={isDataRow ? props.rowIndex : undefined}
- onClick={(e) => props.handleExpand(e, props.data[props.rowIndex], props.itemType)}
+ onClick={createDoubleClickHandler({
+ onDoubleClick: (e) => {
+ props.onItemDoubleClick?.(props.data[props.rowIndex], dataIndex, e);
+ },
+ onSingleClick: (e) => {
+ props.onItemClick?.(props.data[props.rowIndex], dataIndex, e);
+ props.handleExpand(e, props.data[props.rowIndex], props.itemType);
+ },
+ })}
+ onContextMenu={(e) => {
+ props.onItemContextMenu?.(props.data[props.rowIndex], dataIndex, e);
+ }}
ref={containerRef}
style={props.style}
>
@@ -182,6 +195,7 @@ export const TableColumnContainer = (
) => {
const containerRef = useRef(null);
const isDataRow = props.enableHeader && props.rowIndex > 0;
+ const dataIndex = isDataRow ? props.rowIndex - 1 : props.rowIndex;
useEffect(() => {
if (!isDataRow || !containerRef.current || !props.enableRowHover) return;
@@ -223,7 +237,18 @@ export const TableColumnContainer = (
props.enableRowBorders && props.enableHeader && props.rowIndex > 0,
})}
data-row-index={isDataRow ? props.rowIndex : undefined}
- onClick={(e) => props.handleExpand(e, props.data[props.rowIndex], props.itemType)}
+ onClick={createDoubleClickHandler({
+ onDoubleClick: (e) => {
+ props.onItemDoubleClick?.(props.data[props.rowIndex], dataIndex, e);
+ },
+ onSingleClick: (e) => {
+ props.onItemClick?.(props.data[props.rowIndex], dataIndex, e);
+ props.handleExpand(e, props.data[props.rowIndex], props.itemType);
+ },
+ })}
+ onContextMenu={(e) => {
+ props.onItemContextMenu?.(props.data[props.rowIndex], dataIndex, e);
+ }}
ref={containerRef}
style={props.style}
>
diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx
index e09fcb914..92ff22616 100644
--- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx
+++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx
@@ -8,7 +8,6 @@ import {
type JSXElementConstructor,
MouseEvent,
Ref,
- UIEvent,
useCallback,
useEffect,
useMemo,
@@ -33,6 +32,9 @@ export interface CellProps {
enableRowHover?: boolean;
handleExpand: (e: MouseEvent, item: unknown, itemType: LibraryItem) => void;
itemType: LibraryItem;
+ onItemClick?: (item: unknown, index: number, event: MouseEvent) => void;
+ onItemContextMenu?: (item: unknown, index: number, event: MouseEvent) => void;
+ onItemDoubleClick?: (item: unknown, index: number, event: MouseEvent) => void;
size?: 'compact' | 'default';
}
@@ -53,23 +55,19 @@ interface ItemTableListProps {
enableRowHover?: boolean;
enableSelection?: boolean;
headerHeight?: number;
- initialTopMostItemIndex?:
- | number
- | {
- align: 'center' | 'end' | 'start';
- behavior: 'auto' | 'smooth';
- index: number;
- offset?: number;
- };
+ initialTop?: {
+ behavior?: 'auto' | 'smooth';
+ to: number;
+ type: 'index' | 'offset';
+ };
itemType: LibraryItem;
- onCellsRendered: GridProps['onCellsRendered'];
+ onCellsRendered?: GridProps['onCellsRendered'];
onEndReached?: (index: number) => void;
onItemClick?: (item: unknown, index: number, event: MouseEvent) => void;
onItemContextMenu?: (item: unknown, index: number, event: MouseEvent) => void;
onItemDoubleClick?: (item: unknown, index: number, event: MouseEvent) => void;
onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
- onScroll?: (event: UIEvent) => void;
- onScrollEnd?: () => void;
+ onScrollEnd?: (offset: number) => void;
onStartReached?: (index: number) => void;
ref?: Ref;
rowHeight: ((index: number, cellProps: CellProps) => number) | number;
@@ -95,11 +93,12 @@ export const ItemTableList = ({
CellComponent,
columns,
data,
+ enableExpansion = false,
enableHeader = true,
enableRowBorders = false,
enableRowHover = false,
headerHeight = 40,
- initialTopMostItemIndex,
+ initialTop,
itemType,
onCellsRendered,
onEndReached,
@@ -107,7 +106,6 @@ export const ItemTableList = ({
onItemContextMenu,
onItemDoubleClick,
onRangeChanged,
- onScroll,
onScrollEnd,
onStartReached,
ref,
@@ -134,6 +132,8 @@ export const ItemTableList = ({
const [showLeftShadow, setShowLeftShadow] = useState(false);
const [showRightShadow, setShowRightShadow] = useState(false);
+ const onScrollEndRef = useRef(onScrollEnd);
+
const [initialize] = useOverlayScrollbars({
defer: true,
events: {
@@ -230,6 +230,10 @@ export const ItemTableList = ({
const timeout = setTimeout(() => {
scrollingElements.delete(element);
scrollTimeouts.delete(element);
+
+ if (element === row && onScrollEndRef.current) {
+ onScrollEndRef.current(row.scrollTop);
+ }
}, 150);
scrollTimeouts.set(element, timeout);
@@ -446,6 +450,10 @@ export const ItemTableList = ({
const handleExpand = useCallback(
(_e: MouseEvent, item: unknown, itemType: LibraryItem) => {
+ if (!enableExpansion) {
+ return;
+ }
+
if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
internalState.toggleExpanded({
id: item.id as string,
@@ -454,7 +462,7 @@ export const ItemTableList = ({
});
}
},
- [internalState],
+ [enableExpansion, internalState],
);
const handleOnCellsRendered = useCallback(
@@ -469,21 +477,41 @@ export const ItemTableList = ({
startIndex: cells.rowStartIndex,
});
- return onCellsRendered
- ? ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => {
- return onCellsRendered!(
- {
- columnStartIndex: columnStartIndex + pinnedLeftColumnCount,
- columnStopIndex: columnStopIndex + pinnedLeftColumnCount,
- rowStartIndex: rowStartIndex + pinnedRowCount,
- rowStopIndex: rowStopIndex + pinnedRowCount,
- },
- cells,
- );
- }
- : undefined;
+ if (onStartReached || onEndReached) {
+ if (cells.rowStartIndex === 0) {
+ onStartReached?.(0);
+ }
+
+ if (cells.rowStopIndex + 10 >= totalItemCount) {
+ onEndReached?.(totalItemCount);
+ }
+ }
+
+ if (onCellsRendered) {
+ return ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) => {
+ return onCellsRendered!(
+ {
+ columnStartIndex: columnStartIndex + pinnedLeftColumnCount,
+ columnStopIndex: columnStopIndex + pinnedLeftColumnCount,
+ rowStartIndex: rowStartIndex + pinnedRowCount,
+ rowStopIndex: rowStopIndex + pinnedRowCount,
+ },
+ cells,
+ );
+ };
+ }
+
+ return undefined;
},
- [onCellsRendered, onRangeChanged, pinnedLeftColumnCount, pinnedRowCount],
+ [
+ onCellsRendered,
+ onEndReached,
+ onRangeChanged,
+ onStartReached,
+ pinnedLeftColumnCount,
+ pinnedRowCount,
+ totalItemCount,
+ ],
);
const PinnedRowCell = useCallback(
@@ -536,9 +564,6 @@ export const ItemTableList = ({
{
- // onItemClick?.(cellProps.data[cellProps.rowIndex], cellProps.rowIndex, e);
- // }}
rowIndex={cellProps.rowIndex + pinnedRowCount}
/>
);
@@ -554,9 +579,103 @@ export const ItemTableList = ({
enableRowHover,
handleExpand,
itemType,
+ onItemClick,
+ onItemContextMenu,
+ onItemDoubleClick,
size,
};
+ const isInitialScrollPositionSet = useRef(false);
+
+ useEffect(() => {
+ if (!initialTop || isInitialScrollPositionSet.current) return;
+
+ const scrollToIndex = (index: number, behavior: 'auto' | 'smooth' = 'auto') => {
+ isInitialScrollPositionSet.current = true;
+ const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
+
+ // Calculate scroll position based on row heights
+ const calculateScrollTop = (rowIndex: number) => {
+ let scrollTop = 0;
+ for (let i = 0; i < rowIndex; i++) {
+ const height = rowHeight as number;
+ scrollTop += height;
+ }
+ return scrollTop;
+ };
+
+ const scrollTop = calculateScrollTop(adjustedIndex);
+
+ // Get the scroll containers and scroll them directly
+ const mainContainer = rowRef.current?.childNodes[0] as HTMLDivElement;
+ const pinnedLeftContainer = pinnedLeftColumnRef.current
+ ?.childNodes[0] as HTMLDivElement;
+ const pinnedRightContainer = pinnedRightColumnRef.current
+ ?.childNodes[0] as HTMLDivElement;
+
+ if (initialTop.type === 'offset') {
+ if (mainContainer) {
+ mainContainer.scrollTo({
+ behavior,
+ top: initialTop.to,
+ });
+ }
+
+ if (pinnedLeftContainer) {
+ pinnedLeftContainer.scrollTo({
+ behavior,
+ top: initialTop.to,
+ });
+ }
+
+ if (pinnedRightContainer) {
+ pinnedRightContainer.scrollTo({
+ behavior,
+ top: initialTop.to,
+ });
+ }
+ } else {
+ if (mainContainer) {
+ mainContainer.scrollTo({
+ behavior,
+ top: scrollTop,
+ });
+ }
+
+ if (pinnedLeftContainer) {
+ pinnedLeftContainer.scrollTo({
+ behavior,
+ top: scrollTop,
+ });
+ }
+
+ if (pinnedRightContainer) {
+ pinnedRightContainer.scrollTo({
+ behavior,
+ top: scrollTop,
+ });
+ }
+ }
+ };
+
+ scrollToIndex(initialTop.to, initialTop.behavior);
+ }, [initialTop, enableHeader, pinnedLeftColumnCount, pinnedRightColumnCount, rowHeight]);
+
+ // Expose grid refs to parent component
+ useEffect(() => {
+ if (ref && typeof ref === 'object') {
+ // Create a simple API that exposes the main container
+ const combinedAPI: GridImperativeAPI = {
+ // We'll create a minimal API that can be extended later
+ // For now, we'll just expose the main container ref
+ } as GridImperativeAPI;
+
+ if ('current' in ref) {
+ (ref as React.MutableRefObject).current = combinedAPI;
+ }
+ }
+ }, [ref]);
+
return (
{
+ delay?: number;
+ onDoubleClick?: (event: MouseEvent) => void;
+ onSingleClick?: (event: MouseEvent) => void;
+}
+
+/**
+ * Creates a handler that manages single and double-click events,
+ * ensuring double-click doesn't trigger single-click
+ */
+export const createDoubleClickHandler = (
+ options: DoubleClickHandlerOptions,
+) => {
+ const { delay = 200, onDoubleClick, onSingleClick } = options;
+
+ let clickTimeout: NodeJS.Timeout | null = null;
+ let clickCount = 0;
+
+ const handleClick = (event: MouseEvent) => {
+ clickCount++;
+
+ if (clickCount === 1) {
+ // First click - set a timeout to handle single click
+ clickTimeout = setTimeout(() => {
+ if (clickCount === 1) {
+ // Only single click occurred
+ onSingleClick?.(event);
+ }
+ clickCount = 0;
+ clickTimeout = null;
+ }, delay);
+ } else if (clickCount === 2) {
+ // Double click detected
+ if (clickTimeout) {
+ clearTimeout(clickTimeout);
+ clickTimeout = null;
+ }
+
+ onDoubleClick?.(event);
+ clickCount = 0;
+ }
+ };
+
+ return handleClick;
+};