mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
support real-time table column resizing
This commit is contained in:
@@ -179,6 +179,14 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.resize-handle.resize-handle-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.track-header-cell:hover .resize-handle.resize-handle-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -911,8 +911,7 @@ const DetailListHeaderCell = memo(
|
||||
const percent = col ? (columnWidthPercents[colIndex] ?? 0) : 0;
|
||||
const { fixedWidth, isFixedColumn } = getTrackColumnFixed(columnId);
|
||||
const currentWidth = col?.width ?? (fixedWidth || 100);
|
||||
const showResizeHandle =
|
||||
enableColumnResize && !isFixedColumn && !col?.autoSize && onColumnResized;
|
||||
const showResizeHandle = enableColumnResize && !isFixedColumn;
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || !onColumnReordered) {
|
||||
@@ -1026,6 +1025,7 @@ const DetailListHeaderCell = memo(
|
||||
{showResizeHandle && (
|
||||
<DetailListColumnResizeHandle
|
||||
columnId={columnId}
|
||||
disabled={!!col?.autoSize}
|
||||
initialWidth={currentWidth}
|
||||
onResize={handleResize}
|
||||
side="right"
|
||||
@@ -1040,6 +1040,7 @@ DetailListHeaderCell.displayName = 'DetailListHeaderCell';
|
||||
|
||||
interface DetailListColumnResizeHandleProps {
|
||||
columnId: TableColumn;
|
||||
disabled?: boolean;
|
||||
initialWidth: number;
|
||||
onResize: (columnId: TableColumn, width: number) => void;
|
||||
side: 'left' | 'right';
|
||||
@@ -1047,6 +1048,7 @@ interface DetailListColumnResizeHandleProps {
|
||||
|
||||
const DetailListColumnResizeHandle = ({
|
||||
columnId,
|
||||
disabled = false,
|
||||
initialWidth,
|
||||
onResize,
|
||||
side,
|
||||
@@ -1091,6 +1093,11 @@ const DetailListColumnResizeHandle = ({
|
||||
}, [isDragging, columnId, onResize]);
|
||||
|
||||
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (disabled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsDragging(true);
|
||||
@@ -1103,6 +1110,7 @@ const DetailListColumnResizeHandle = ({
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.resizeHandle, {
|
||||
[styles.resizeHandleDisabled]: disabled,
|
||||
[styles.resizeHandleDragging]: isDragging,
|
||||
[styles.resizeHandleLeft]: side === 'left',
|
||||
[styles.resizeHandleRight]: side === 'right',
|
||||
|
||||
@@ -366,6 +366,14 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.resize-handle.resize-handle-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.header-container:hover .resize-handle.resize-handle-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table
|
||||
import { YearColumn } from '/@/renderer/components/item-list/item-table-list/columns/year-column';
|
||||
import { useItemDragDropState } from '/@/renderer/components/item-list/item-table-list/hooks/use-item-drag-drop-state';
|
||||
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||
import { useItemTableListColumnResizeLive } from '/@/renderer/components/item-list/item-table-list/item-table-list-context';
|
||||
import { ItemControls, ItemListItem } from '/@/renderer/components/item-list/types';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
@@ -707,6 +708,8 @@ export const TableColumnContainer = (
|
||||
|
||||
interface ColumnResizeHandleProps {
|
||||
columnId: TableColumn;
|
||||
columnIndex: number;
|
||||
disabled?: boolean;
|
||||
initialWidth: number;
|
||||
onResize: (columnId: TableColumn, width: number) => void;
|
||||
side: 'left' | 'right';
|
||||
@@ -714,6 +717,8 @@ interface ColumnResizeHandleProps {
|
||||
|
||||
const ColumnResizeHandle = ({
|
||||
columnId,
|
||||
columnIndex,
|
||||
disabled = false,
|
||||
initialWidth,
|
||||
onResize,
|
||||
side,
|
||||
@@ -723,6 +728,17 @@ const ColumnResizeHandle = ({
|
||||
const startWidthRef = useRef<number>(initialWidth);
|
||||
const startXRef = useRef<number>(0);
|
||||
const finalWidthRef = useRef<number>(initialWidth);
|
||||
const columnResizeLive = useItemTableListColumnResizeLive();
|
||||
const onResizeRef = useRef(onResize);
|
||||
const columnResizeLiveRef = useRef(columnResizeLive);
|
||||
|
||||
useEffect(() => {
|
||||
onResizeRef.current = onResize;
|
||||
}, [onResize]);
|
||||
|
||||
useEffect(() => {
|
||||
columnResizeLiveRef.current = columnResizeLive;
|
||||
}, [columnResizeLive]);
|
||||
|
||||
// Update the ref when initialWidth changes (but not during drag)
|
||||
useEffect(() => {
|
||||
@@ -738,6 +754,7 @@ const ColumnResizeHandle = ({
|
||||
const deltaX = event.clientX - startXRef.current;
|
||||
const newWidth = Math.min(Math.max(10, startWidthRef.current + deltaX), 1000);
|
||||
finalWidthRef.current = newWidth;
|
||||
columnResizeLiveRef.current?.scheduleColumnResizePreview(columnIndex, newWidth);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
@@ -746,7 +763,8 @@ const ColumnResizeHandle = ({
|
||||
document.body.style.userSelect = '';
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
onResize(columnId, finalWidthRef.current);
|
||||
onResizeRef.current(columnId, finalWidthRef.current);
|
||||
columnResizeLiveRef.current?.clearColumnResizePreview();
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
@@ -755,10 +773,18 @@ const ColumnResizeHandle = ({
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
columnResizeLiveRef.current?.clearColumnResizePreview();
|
||||
};
|
||||
}, [isDragging, columnId, onResize]);
|
||||
}, [isDragging, columnId, columnIndex]);
|
||||
|
||||
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (disabled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsDragging(true);
|
||||
@@ -771,6 +797,7 @@ const ColumnResizeHandle = ({
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.resizeHandle, {
|
||||
[styles.resizeHandleDisabled]: disabled,
|
||||
[styles.resizeHandleDragging]: isDragging,
|
||||
[styles.resizeHandleLeft]: side === 'left',
|
||||
[styles.resizeHandleRight]: side === 'right',
|
||||
@@ -917,9 +944,11 @@ export const TableColumnHeaderContainer = (
|
||||
>
|
||||
{columnLabelMap[props.type]}
|
||||
</Text>
|
||||
{!columnConfig.autoSize && props.enableColumnResize && (
|
||||
{props.enableColumnResize && (
|
||||
<ColumnResizeHandle
|
||||
columnId={props.type}
|
||||
columnIndex={props.columnIndex}
|
||||
disabled={!!columnConfig.autoSize}
|
||||
initialWidth={currentWidth}
|
||||
onResize={handleResize}
|
||||
side="right"
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
import type { TableItemProps } from './item-table-list';
|
||||
@@ -68,6 +76,69 @@ export const useItemTableListConfig = (): ItemTableListConfig | null => {
|
||||
return useContext(ItemTableListConfigContext);
|
||||
};
|
||||
|
||||
export type ItemTableListColumnResizeLiveContextValue = {
|
||||
clearColumnResizePreview: () => void;
|
||||
scheduleColumnResizePreview: (columnIndex: number, width: number) => void;
|
||||
};
|
||||
|
||||
const ItemTableListColumnResizeLiveContext =
|
||||
createContext<ItemTableListColumnResizeLiveContextValue | null>(null);
|
||||
|
||||
export const ItemTableListColumnResizeLiveProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
value: ItemTableListColumnResizeLiveContextValue;
|
||||
}) => {
|
||||
return (
|
||||
<ItemTableListColumnResizeLiveContext.Provider value={value}>
|
||||
{children}
|
||||
</ItemTableListColumnResizeLiveContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useItemTableListColumnResizeLive =
|
||||
(): ItemTableListColumnResizeLiveContextValue | null => {
|
||||
return useContext(ItemTableListColumnResizeLiveContext);
|
||||
};
|
||||
|
||||
export const useItemTableListColumnResizeLiveState = () => {
|
||||
const [columnResizePreview, setColumnResizePreview] = useState<null | {
|
||||
columnIndex: number;
|
||||
width: number;
|
||||
}>(null);
|
||||
const previewRafRef = useRef<null | number>(null);
|
||||
const pendingPreviewRef = useRef<null | { columnIndex: number; width: number }>(null);
|
||||
|
||||
const scheduleColumnResizePreview = useCallback((columnIndex: number, width: number) => {
|
||||
pendingPreviewRef.current = { columnIndex, width };
|
||||
if (previewRafRef.current !== null) return;
|
||||
previewRafRef.current = requestAnimationFrame(() => {
|
||||
previewRafRef.current = null;
|
||||
const pending = pendingPreviewRef.current;
|
||||
if (pending) {
|
||||
setColumnResizePreview(pending);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearColumnResizePreview = useCallback(() => {
|
||||
if (previewRafRef.current !== null) {
|
||||
cancelAnimationFrame(previewRafRef.current);
|
||||
previewRafRef.current = null;
|
||||
}
|
||||
pendingPreviewRef.current = null;
|
||||
setColumnResizePreview(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
clearColumnResizePreview,
|
||||
columnResizePreview,
|
||||
scheduleColumnResizePreview,
|
||||
};
|
||||
};
|
||||
|
||||
type ItemTableListStoreContextValue = {
|
||||
activeRowStore: ActiveRowStore;
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
z-index: 15;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
background-color: var(--theme-colors-background);
|
||||
border-bottom: 1px solid var(--theme-colors-border);
|
||||
@@ -168,6 +168,7 @@
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
|
||||
@@ -45,9 +45,11 @@ import { useTableRowModel } from '/@/renderer/components/item-list/item-table-li
|
||||
import { useTableScrollToIndex } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index';
|
||||
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import {
|
||||
ItemTableListColumnResizeLiveProvider,
|
||||
type ItemTableListConfig,
|
||||
ItemTableListConfigProvider,
|
||||
ItemTableListStoreProvider,
|
||||
useItemTableListColumnResizeLiveState,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-context';
|
||||
import {
|
||||
MemoizedCellRouter,
|
||||
@@ -541,7 +543,7 @@ const VirtualizedTableGrid = ({
|
||||
})}
|
||||
style={{
|
||||
minHeight: `${pinnedRowsMinHeightPx}px`,
|
||||
overflow: 'hidden',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
@@ -659,7 +661,7 @@ const VirtualizedTableGrid = ({
|
||||
})}
|
||||
style={{
|
||||
minHeight: `${pinnedRowsMinHeightPx}px`,
|
||||
overflow: 'hidden',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
@@ -968,7 +970,7 @@ const ItemTableListStickyUI = memo(
|
||||
style={{
|
||||
flex: '0 1 auto',
|
||||
minWidth: `${pinnedLeftWidth}px`,
|
||||
overflow: 'hidden',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
>
|
||||
{parsedColumns
|
||||
@@ -1052,7 +1054,7 @@ const ItemTableListStickyUI = memo(
|
||||
style={{
|
||||
flex: '0 1 auto',
|
||||
minWidth: `${pinnedRightWidth}px`,
|
||||
overflow: 'hidden',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
>
|
||||
{parsedColumns
|
||||
@@ -1288,6 +1290,30 @@ const BaseItemTableList = ({
|
||||
columns,
|
||||
totalContainerWidth,
|
||||
});
|
||||
|
||||
const { clearColumnResizePreview, columnResizePreview, scheduleColumnResizePreview } =
|
||||
useItemTableListColumnResizeLiveState();
|
||||
|
||||
const columnResizeLiveValue = useMemo(
|
||||
() => ({
|
||||
clearColumnResizePreview,
|
||||
scheduleColumnResizePreview,
|
||||
}),
|
||||
[clearColumnResizePreview, scheduleColumnResizePreview],
|
||||
);
|
||||
|
||||
const displayColumnWidths = useMemo(() => {
|
||||
if (!columnResizePreview) {
|
||||
return calculatedColumnWidths;
|
||||
}
|
||||
const next = calculatedColumnWidths.slice();
|
||||
const { columnIndex, width } = columnResizePreview;
|
||||
if (columnIndex >= 0 && columnIndex < next.length) {
|
||||
next[columnIndex] = width;
|
||||
}
|
||||
return next;
|
||||
}, [calculatedColumnWidths, columnResizePreview]);
|
||||
|
||||
const playerContext = usePlayer();
|
||||
|
||||
const {
|
||||
@@ -1505,7 +1531,7 @@ const BaseItemTableList = ({
|
||||
// Create itemProps for sticky header
|
||||
const stickyHeaderItemProps: TableItemProps = useMemo(
|
||||
() => ({
|
||||
calculatedColumnWidths,
|
||||
calculatedColumnWidths: displayColumnWidths,
|
||||
cellPadding,
|
||||
columns: parsedColumns,
|
||||
controls,
|
||||
@@ -1525,9 +1551,9 @@ const BaseItemTableList = ({
|
||||
internalState,
|
||||
itemType,
|
||||
pinnedLeftColumnCount,
|
||||
pinnedLeftColumnWidths: calculatedColumnWidths.slice(0, pinnedLeftColumnCount),
|
||||
pinnedLeftColumnWidths: displayColumnWidths.slice(0, pinnedLeftColumnCount),
|
||||
pinnedRightColumnCount,
|
||||
pinnedRightColumnWidths: calculatedColumnWidths.slice(
|
||||
pinnedRightColumnWidths: displayColumnWidths.slice(
|
||||
pinnedLeftColumnCount + totalColumnCount,
|
||||
),
|
||||
playerContext,
|
||||
@@ -1536,7 +1562,7 @@ const BaseItemTableList = ({
|
||||
tableId,
|
||||
}),
|
||||
[
|
||||
calculatedColumnWidths,
|
||||
displayColumnWidths,
|
||||
cellPadding,
|
||||
controls,
|
||||
parsedColumns,
|
||||
@@ -1641,9 +1667,7 @@ const BaseItemTableList = ({
|
||||
};
|
||||
}, [CellComponent, columnCellComponents]);
|
||||
|
||||
return (
|
||||
<ItemTableListStoreProvider activeRowId={activeRowId}>
|
||||
<ItemTableListConfigProvider value={tableConfigValue}>
|
||||
const tableMotion = (
|
||||
<motion.div
|
||||
className={styles.itemTableListContainer}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -1660,7 +1684,7 @@ const BaseItemTableList = ({
|
||||
transition={{ duration: enableEntranceAnimation ? 0.3 : 0, ease: 'anticipate' }}
|
||||
>
|
||||
<ItemTableListStickyUI
|
||||
calculatedColumnWidths={calculatedColumnWidths}
|
||||
calculatedColumnWidths={displayColumnWidths}
|
||||
CellComponent={optimizedCellComponent}
|
||||
containerRef={containerRef}
|
||||
data={data}
|
||||
@@ -1684,7 +1708,7 @@ const BaseItemTableList = ({
|
||||
totalColumnCount={totalColumnCount}
|
||||
/>
|
||||
<MemoizedVirtualizedTableGrid
|
||||
calculatedColumnWidths={calculatedColumnWidths}
|
||||
calculatedColumnWidths={displayColumnWidths}
|
||||
CellComponent={optimizedCellComponent}
|
||||
data={data}
|
||||
dataWithGroups={dataWithGroups}
|
||||
@@ -1706,6 +1730,18 @@ const BaseItemTableList = ({
|
||||
totalRowCount={totalRowCount}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ItemTableListStoreProvider activeRowId={activeRowId}>
|
||||
<ItemTableListConfigProvider value={tableConfigValue}>
|
||||
{onColumnResized ? (
|
||||
<ItemTableListColumnResizeLiveProvider value={columnResizeLiveValue}>
|
||||
{tableMotion}
|
||||
</ItemTableListColumnResizeLiveProvider>
|
||||
) : (
|
||||
tableMotion
|
||||
)}
|
||||
</ItemTableListConfigProvider>
|
||||
</ItemTableListStoreProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user