mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
use external store for scroll shadow
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import type { TableScrollShadowStore } from '/@/renderer/components/item-list/item-table-list/table-scroll-shadow-store';
|
||||||
|
|
||||||
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
|
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
@@ -18,9 +20,7 @@ export const useTablePaneSync = ({
|
|||||||
pinnedRowRef,
|
pinnedRowRef,
|
||||||
rowRef,
|
rowRef,
|
||||||
scrollContainerRef,
|
scrollContainerRef,
|
||||||
setShowLeftShadow,
|
scrollShadowStore,
|
||||||
setShowRightShadow,
|
|
||||||
setShowTopShadow,
|
|
||||||
}: {
|
}: {
|
||||||
enableDrag: boolean | undefined;
|
enableDrag: boolean | undefined;
|
||||||
enableDragScroll: boolean | undefined;
|
enableDragScroll: boolean | undefined;
|
||||||
@@ -36,9 +36,7 @@ export const useTablePaneSync = ({
|
|||||||
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
|
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
|
||||||
rowRef: React.RefObject<HTMLDivElement | null>;
|
rowRef: React.RefObject<HTMLDivElement | null>;
|
||||||
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
setShowLeftShadow: (v: boolean) => void;
|
scrollShadowStore: TableScrollShadowStore;
|
||||||
setShowRightShadow: (v: boolean) => void;
|
|
||||||
setShowTopShadow: (v: boolean) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
// Main grid overlayscrollbars - only handle X-axis if right-pinned columns exist
|
// Main grid overlayscrollbars - only handle X-axis if right-pinned columns exist
|
||||||
const [initialize, osInstance] = useOverlayScrollbars({
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
@@ -471,8 +469,10 @@ export const useTablePaneSync = ({
|
|||||||
|
|
||||||
if (!row) {
|
if (!row) {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setShowLeftShadow(false);
|
scrollShadowStore.setSnapshot({
|
||||||
setShowRightShadow(false);
|
showLeftShadow: false,
|
||||||
|
showRightShadow: false,
|
||||||
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
@@ -482,8 +482,10 @@ export const useTablePaneSync = ({
|
|||||||
const scrollLeft = row.scrollLeft;
|
const scrollLeft = row.scrollLeft;
|
||||||
const maxScrollLeft = row.scrollWidth - row.clientWidth;
|
const maxScrollLeft = row.scrollWidth - row.clientWidth;
|
||||||
|
|
||||||
setShowLeftShadow(pinnedLeftColumnCount > 0 && scrollLeft > 0);
|
scrollShadowStore.setSnapshot({
|
||||||
setShowRightShadow(pinnedRightColumnCount > 0 && scrollLeft < maxScrollLeft);
|
showLeftShadow: pinnedLeftColumnCount > 0 && scrollLeft > 0,
|
||||||
|
showRightShadow: pinnedRightColumnCount > 0 && scrollLeft < maxScrollLeft,
|
||||||
|
});
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
checkScrollPosition();
|
checkScrollPosition();
|
||||||
@@ -494,13 +496,7 @@ export const useTablePaneSync = ({
|
|||||||
checkScrollPosition.cancel();
|
checkScrollPosition.cancel();
|
||||||
row.removeEventListener('scroll', checkScrollPosition);
|
row.removeEventListener('scroll', checkScrollPosition);
|
||||||
};
|
};
|
||||||
}, [
|
}, [pinnedLeftColumnCount, pinnedRightColumnCount, rowRef, scrollShadowStore]);
|
||||||
pinnedLeftColumnCount,
|
|
||||||
pinnedRightColumnCount,
|
|
||||||
rowRef,
|
|
||||||
setShowLeftShadow,
|
|
||||||
setShowRightShadow,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle top shadow visibility based on vertical scroll
|
// Handle top shadow visibility based on vertical scroll
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -509,7 +505,7 @@ export const useTablePaneSync = ({
|
|||||||
|
|
||||||
if (!row || !enableHeader) {
|
if (!row || !enableHeader) {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setShowTopShadow(false);
|
scrollShadowStore.setSnapshot({ showTopShadow: false });
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
@@ -519,7 +515,7 @@ export const useTablePaneSync = ({
|
|||||||
|
|
||||||
const checkScrollPosition = throttle(() => {
|
const checkScrollPosition = throttle(() => {
|
||||||
const currentScrollTop = scrollElement.scrollTop;
|
const currentScrollTop = scrollElement.scrollTop;
|
||||||
setShowTopShadow(currentScrollTop > 0);
|
scrollShadowStore.setSnapshot({ showTopShadow: currentScrollTop > 0 });
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
checkScrollPosition();
|
checkScrollPosition();
|
||||||
@@ -530,5 +526,5 @@ export const useTablePaneSync = ({
|
|||||||
checkScrollPosition.cancel();
|
checkScrollPosition.cancel();
|
||||||
scrollElement.removeEventListener('scroll', checkScrollPosition);
|
scrollElement.removeEventListener('scroll', checkScrollPosition);
|
||||||
};
|
};
|
||||||
}, [enableHeader, pinnedRightColumnCount, pinnedRightColumnRef, rowRef, setShowTopShadow]);
|
}, [enableHeader, pinnedRightColumnCount, pinnedRightColumnRef, rowRef, scrollShadowStore]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
|
useSyncExternalStore,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { type CellComponentProps, Grid } from 'react-window-v2';
|
import { type CellComponentProps, Grid } from 'react-window-v2';
|
||||||
@@ -52,6 +53,10 @@ import {
|
|||||||
MemoizedCellRouter,
|
MemoizedCellRouter,
|
||||||
useColumnCellComponents,
|
useColumnCellComponents,
|
||||||
} from '/@/renderer/components/item-list/item-table-list/memoized-cell-router';
|
} from '/@/renderer/components/item-list/item-table-list/memoized-cell-router';
|
||||||
|
import {
|
||||||
|
createTableScrollShadowStore,
|
||||||
|
type TableScrollShadowStore,
|
||||||
|
} from '/@/renderer/components/item-list/item-table-list/table-scroll-shadow-store';
|
||||||
import {
|
import {
|
||||||
ItemControls,
|
ItemControls,
|
||||||
ItemListHandle,
|
ItemListHandle,
|
||||||
@@ -103,6 +108,63 @@ export enum TableItemSize {
|
|||||||
LARGE = 88,
|
LARGE = 88,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ItemTableScrollShadowTop = memo(function ItemTableScrollShadowTop({
|
||||||
|
enableHeader,
|
||||||
|
enableScrollShadow,
|
||||||
|
scrollShadowStore,
|
||||||
|
}: {
|
||||||
|
enableHeader: boolean;
|
||||||
|
enableScrollShadow: boolean;
|
||||||
|
scrollShadowStore: TableScrollShadowStore;
|
||||||
|
}) {
|
||||||
|
const { showTopShadow } = useSyncExternalStore(
|
||||||
|
scrollShadowStore.subscribe,
|
||||||
|
scrollShadowStore.getSnapshot,
|
||||||
|
);
|
||||||
|
if (!enableHeader || !enableScrollShadow || !showTopShadow) return null;
|
||||||
|
return <div className={styles.itemTableTopScrollShadow} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemTableScrollShadowTop.displayName = 'ItemTableScrollShadowTop';
|
||||||
|
|
||||||
|
const ItemTableScrollShadowLeft = memo(function ItemTableScrollShadowLeft({
|
||||||
|
enableScrollShadow,
|
||||||
|
pinnedLeftColumnCount,
|
||||||
|
scrollShadowStore,
|
||||||
|
}: {
|
||||||
|
enableScrollShadow: boolean;
|
||||||
|
pinnedLeftColumnCount: number;
|
||||||
|
scrollShadowStore: TableScrollShadowStore;
|
||||||
|
}) {
|
||||||
|
const { showLeftShadow } = useSyncExternalStore(
|
||||||
|
scrollShadowStore.subscribe,
|
||||||
|
scrollShadowStore.getSnapshot,
|
||||||
|
);
|
||||||
|
if (pinnedLeftColumnCount <= 0 || !enableScrollShadow || !showLeftShadow) return null;
|
||||||
|
return <div className={styles.itemTableLeftScrollShadow} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemTableScrollShadowLeft.displayName = 'ItemTableScrollShadowLeft';
|
||||||
|
|
||||||
|
const ItemTableScrollShadowRight = memo(function ItemTableScrollShadowRight({
|
||||||
|
enableScrollShadow,
|
||||||
|
pinnedRightColumnCount,
|
||||||
|
scrollShadowStore,
|
||||||
|
}: {
|
||||||
|
enableScrollShadow: boolean;
|
||||||
|
pinnedRightColumnCount: number;
|
||||||
|
scrollShadowStore: TableScrollShadowStore;
|
||||||
|
}) {
|
||||||
|
const { showRightShadow } = useSyncExternalStore(
|
||||||
|
scrollShadowStore.subscribe,
|
||||||
|
scrollShadowStore.getSnapshot,
|
||||||
|
);
|
||||||
|
if (pinnedRightColumnCount <= 0 || !enableScrollShadow || !showRightShadow) return null;
|
||||||
|
return <div className={styles.itemTableRightScrollShadow} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemTableScrollShadowRight.displayName = 'ItemTableScrollShadowRight';
|
||||||
|
|
||||||
interface VirtualizedTableGridProps {
|
interface VirtualizedTableGridProps {
|
||||||
calculatedColumnWidths: number[];
|
calculatedColumnWidths: number[];
|
||||||
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
CellComponent: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
||||||
@@ -120,9 +182,7 @@ interface VirtualizedTableGridProps {
|
|||||||
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
||||||
pinnedRowCount: number;
|
pinnedRowCount: number;
|
||||||
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
|
pinnedRowRef: React.RefObject<HTMLDivElement | null>;
|
||||||
showLeftShadow: boolean;
|
scrollShadowStore: TableScrollShadowStore;
|
||||||
showRightShadow: boolean;
|
|
||||||
showTopShadow: boolean;
|
|
||||||
tableConfig: ItemTableListConfig;
|
tableConfig: ItemTableListConfig;
|
||||||
totalColumnCount: number;
|
totalColumnCount: number;
|
||||||
totalRowCount: number;
|
totalRowCount: number;
|
||||||
@@ -145,9 +205,7 @@ const VirtualizedTableGrid = ({
|
|||||||
pinnedRightColumnRef,
|
pinnedRightColumnRef,
|
||||||
pinnedRowCount,
|
pinnedRowCount,
|
||||||
pinnedRowRef,
|
pinnedRowRef,
|
||||||
showLeftShadow,
|
scrollShadowStore,
|
||||||
showRightShadow,
|
|
||||||
showTopShadow,
|
|
||||||
tableConfig,
|
tableConfig,
|
||||||
totalColumnCount,
|
totalColumnCount,
|
||||||
totalRowCount,
|
totalRowCount,
|
||||||
@@ -497,9 +555,11 @@ const VirtualizedTableGrid = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{enableHeader && enableScrollShadow && showTopShadow && (
|
<ItemTableScrollShadowTop
|
||||||
<div className={styles.itemTableTopScrollShadow} />
|
enableHeader={!!enableHeader}
|
||||||
)}
|
enableScrollShadow={enableScrollShadow}
|
||||||
|
scrollShadowStore={scrollShadowStore}
|
||||||
|
/>
|
||||||
{!!pinnedLeftColumnCount && (
|
{!!pinnedLeftColumnCount && (
|
||||||
<div
|
<div
|
||||||
className={styles.itemTablePinnedColumnsContainer}
|
className={styles.itemTablePinnedColumnsContainer}
|
||||||
@@ -554,9 +614,11 @@ const VirtualizedTableGrid = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{enableHeader && enableScrollShadow && showTopShadow && (
|
<ItemTableScrollShadowTop
|
||||||
<div className={styles.itemTableTopScrollShadow} />
|
enableHeader={!!enableHeader}
|
||||||
)}
|
enableScrollShadow={enableScrollShadow}
|
||||||
|
scrollShadowStore={scrollShadowStore}
|
||||||
|
/>
|
||||||
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
|
<div className={styles.itemTableGridContainer} ref={mergedRowRef}>
|
||||||
<Grid
|
<Grid
|
||||||
cellComponent={RowCell}
|
cellComponent={RowCell}
|
||||||
@@ -568,12 +630,16 @@ const VirtualizedTableGrid = ({
|
|||||||
rowCount={totalRowCount}
|
rowCount={totalRowCount}
|
||||||
rowHeight={rowHeightMemoized}
|
rowHeight={rowHeightMemoized}
|
||||||
/>
|
/>
|
||||||
{pinnedLeftColumnCount > 0 && enableScrollShadow && showLeftShadow && (
|
<ItemTableScrollShadowLeft
|
||||||
<div className={styles.itemTableLeftScrollShadow} />
|
enableScrollShadow={enableScrollShadow}
|
||||||
)}
|
pinnedLeftColumnCount={pinnedLeftColumnCount}
|
||||||
{pinnedRightColumnCount > 0 && enableScrollShadow && showRightShadow && (
|
scrollShadowStore={scrollShadowStore}
|
||||||
<div className={styles.itemTableRightScrollShadow} />
|
/>
|
||||||
)}
|
<ItemTableScrollShadowRight
|
||||||
|
enableScrollShadow={enableScrollShadow}
|
||||||
|
pinnedRightColumnCount={pinnedRightColumnCount}
|
||||||
|
scrollShadowStore={scrollShadowStore}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!pinnedRightColumnCount && (
|
{!!pinnedRightColumnCount && (
|
||||||
@@ -611,9 +677,11 @@ const VirtualizedTableGrid = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{enableHeader && enableScrollShadow && showTopShadow && (
|
<ItemTableScrollShadowTop
|
||||||
<div className={styles.itemTableTopScrollShadow} />
|
enableHeader={!!enableHeader}
|
||||||
)}
|
enableScrollShadow={enableScrollShadow}
|
||||||
|
scrollShadowStore={scrollShadowStore}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className={styles.itemTablePinnedRightColumnsContainer}
|
className={styles.itemTablePinnedRightColumnsContainer}
|
||||||
ref={pinnedRightColumnRef}
|
ref={pinnedRightColumnRef}
|
||||||
@@ -666,9 +734,7 @@ const MemoizedVirtualizedTableGrid = memo(VirtualizedTableGrid, (prevProps, next
|
|||||||
prevProps.pinnedRightColumnRef === nextProps.pinnedRightColumnRef &&
|
prevProps.pinnedRightColumnRef === nextProps.pinnedRightColumnRef &&
|
||||||
prevProps.pinnedRowCount === nextProps.pinnedRowCount &&
|
prevProps.pinnedRowCount === nextProps.pinnedRowCount &&
|
||||||
prevProps.pinnedRowRef === nextProps.pinnedRowRef &&
|
prevProps.pinnedRowRef === nextProps.pinnedRowRef &&
|
||||||
prevProps.showLeftShadow === nextProps.showLeftShadow &&
|
prevProps.scrollShadowStore === nextProps.scrollShadowStore &&
|
||||||
prevProps.showRightShadow === nextProps.showRightShadow &&
|
|
||||||
prevProps.showTopShadow === nextProps.showTopShadow &&
|
|
||||||
prevProps.totalColumnCount === nextProps.totalColumnCount &&
|
prevProps.totalColumnCount === nextProps.totalColumnCount &&
|
||||||
prevProps.totalRowCount === nextProps.totalRowCount &&
|
prevProps.totalRowCount === nextProps.totalRowCount &&
|
||||||
prevProps.CellComponent === nextProps.CellComponent
|
prevProps.CellComponent === nextProps.CellComponent
|
||||||
@@ -1257,9 +1323,7 @@ const BaseItemTableList = ({
|
|||||||
const pinnedRightColumnRef = useRef<HTMLDivElement>(null);
|
const pinnedRightColumnRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const mergedRowRef = useMergedRef(rowRef, scrollContainerRef);
|
const mergedRowRef = useMergedRef(rowRef, scrollContainerRef);
|
||||||
const [showLeftShadow, setShowLeftShadow] = useState(false);
|
const scrollShadowStore = useMemo(() => createTableScrollShadowStore(), []);
|
||||||
const [showRightShadow, setShowRightShadow] = useState(false);
|
|
||||||
const [showTopShadow, setShowTopShadow] = useState(false);
|
|
||||||
const handleRef = useRef<ItemListHandle | null>(null);
|
const handleRef = useRef<ItemListHandle | null>(null);
|
||||||
const { focused, ref: focusRef } = useFocusWithin();
|
const { focused, ref: focusRef } = useFocusWithin();
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -1317,9 +1381,7 @@ const BaseItemTableList = ({
|
|||||||
pinnedRowRef,
|
pinnedRowRef,
|
||||||
rowRef,
|
rowRef,
|
||||||
scrollContainerRef,
|
scrollContainerRef,
|
||||||
setShowLeftShadow,
|
scrollShadowStore,
|
||||||
setShowRightShadow,
|
|
||||||
setShowTopShadow,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRowHeight = useCallback(
|
const getRowHeight = useCallback(
|
||||||
@@ -1638,9 +1700,7 @@ const BaseItemTableList = ({
|
|||||||
pinnedRightColumnRef={pinnedRightColumnRef}
|
pinnedRightColumnRef={pinnedRightColumnRef}
|
||||||
pinnedRowCount={pinnedRowCount}
|
pinnedRowCount={pinnedRowCount}
|
||||||
pinnedRowRef={pinnedRowRef}
|
pinnedRowRef={pinnedRowRef}
|
||||||
showLeftShadow={showLeftShadow}
|
scrollShadowStore={scrollShadowStore}
|
||||||
showRightShadow={showRightShadow}
|
|
||||||
showTopShadow={showTopShadow}
|
|
||||||
tableConfig={tableConfigValue}
|
tableConfig={tableConfigValue}
|
||||||
totalColumnCount={totalColumnCount}
|
totalColumnCount={totalColumnCount}
|
||||||
totalRowCount={totalRowCount}
|
totalRowCount={totalRowCount}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export interface TableScrollShadowSnapshot {
|
||||||
|
showLeftShadow: boolean;
|
||||||
|
showRightShadow: boolean;
|
||||||
|
showTopShadow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TableScrollShadowStore = ReturnType<typeof createTableScrollShadowStore>;
|
||||||
|
|
||||||
|
export function createTableScrollShadowStore() {
|
||||||
|
let snapshot: TableScrollShadowSnapshot = {
|
||||||
|
showLeftShadow: false,
|
||||||
|
showRightShadow: false,
|
||||||
|
showTopShadow: false,
|
||||||
|
};
|
||||||
|
const listeners = new Set<() => void>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSnapshot: (): TableScrollShadowSnapshot => snapshot,
|
||||||
|
setSnapshot: (patch: Partial<TableScrollShadowSnapshot>) => {
|
||||||
|
const next: TableScrollShadowSnapshot = { ...snapshot, ...patch };
|
||||||
|
if (
|
||||||
|
next.showLeftShadow === snapshot.showLeftShadow &&
|
||||||
|
next.showRightShadow === snapshot.showRightShadow &&
|
||||||
|
next.showTopShadow === snapshot.showTopShadow
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshot = next;
|
||||||
|
listeners.forEach((l) => l());
|
||||||
|
},
|
||||||
|
subscribe: (listener: () => void) => {
|
||||||
|
listeners.add(listener);
|
||||||
|
return () => listeners.delete(listener);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user