Skip to content

Commit

Permalink
Adding the option for scrollers in both axis
Browse files Browse the repository at this point in the history
  • Loading branch information
moransantiago committed Oct 1, 2024
1 parent 69e4b38 commit 81aeb2f
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 72 deletions.
3 changes: 2 additions & 1 deletion packages/cells/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"@linaria/react": "^4.5.3",
"@toast-ui/editor": "3.1.10",
"@toast-ui/react-editor": "3.1.10",
"react-select": "^5.8.0"
"react-select": "^5.8.0",
"@glideapps/glide-data-grid": "6.0.4-alpha11"
},
"devDependencies": {
"@babel/cli": "^7.16.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@basehub/glide-data-grid",
"version": "6.0.4-alpha10",
"version": "6.0.4-alpha11",
"description": "React data grid for beautifully displaying and editing large amounts of data with amazing performance.",
"sideEffects": [
"**/*.css"
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/data-editor-all.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const DataEditorAllImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEdito
headerIcons={allSprites}
ref={ref}
imageWindowLoader={imageWindowLoader}
scrollerRef={p.scrollerRef}
scrollerYRef={p.scrollerYRef}
scrollerXRef={p.scrollerXRef}
scrollOffsetTop={p.scrollOffsetTop}
scrollOffsetBottom={p.scrollOffsetBottom}
/>
Expand Down
11 changes: 6 additions & 5 deletions packages/core/src/data-editor/data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function shiftSelection(input: GridSelection, offset: number): GridSelection {
*/
export interface DataEditorProps
extends Props,
Pick<DataGridSearchProps, "imageWindowLoader" | "scrollOffsetTop" | "scrollerRef"> {
Pick<DataGridSearchProps, "imageWindowLoader" | "scrollOffsetTop" | "scrollerYRef" | "scrollerXRef"> {
/** Emitted whenever the user has requested the deletion of the selection.
* @group Editing
*/
Expand Down Expand Up @@ -1293,7 +1293,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
//If the grid is empty, we will return text
const isFirst = col === rowMarkerOffset;

const maybeFirstColumnHint = isFirst ? trailingRowOptions?.hint ?? "" : "";
const maybeFirstColumnHint = isFirst ? (trailingRowOptions?.hint ?? "") : "";
const c = mangledColsRef.current[col];

if (c?.trailingRowOptions?.disabled === true) {
Expand Down Expand Up @@ -3418,7 +3418,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
formatted?: string | string[]
): EditListItem | undefined {
const stringifiedRawValue =
typeof rawValue === "object" ? rawValue?.join("\n") ?? "" : rawValue?.toString() ?? "";
typeof rawValue === "object" ? (rawValue?.join("\n") ?? "") : (rawValue?.toString() ?? "");

if (!isInnerOnlyCell(inner) && isReadWriteCell(inner) && inner.readonly !== true) {
const coerced = coercePasteValue?.(stringifiedRawValue, inner);
Expand Down Expand Up @@ -3796,7 +3796,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
(col: number) => {
return typeof verticalBorder === "boolean"
? verticalBorder
: verticalBorder?.(col - rowMarkerOffset) ?? true;
: (verticalBorder?.(col - rowMarkerOffset) ?? true);
},
[rowMarkerOffset, verticalBorder]
);
Expand Down Expand Up @@ -4021,7 +4021,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
inWidth={width ?? idealWidth}
inHeight={height ?? idealHeight}>
<DataGridSearch
scrollerRef={p.scrollerRef}
scrollerXRef={p.scrollerXRef}
scrollerYRef={p.scrollerYRef}
scrollOffsetTop={p.scrollOffsetTop}
scrollOffsetBottom={p.scrollOffsetBottom}
fillHandle={fillHandle}
Expand Down
108 changes: 87 additions & 21 deletions packages/core/src/docs/examples/add-column.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,66 @@ interface AddColumnsProps {
const rowCount = 150;

export const AddColumns: React.FC<AddColumnsProps> = p => {
const { cols, getCellContent } = useMockDataGenerator(p.columnsCount);
const scrollerRef = useRef<HTMLDivElement>(null);
const { cols, getCellContent } = useMockDataGenerator(100);
const fakeScrollerRef = useRef<HTMLDivElement>(null);
const scrollerXRef = useRef<HTMLDivElement>(null);
const scrollerYRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLElement>(null);

const offsetBottom = 150;
const height = "500px";
const rowHeight = 32;

const offsetLeft = 56;

React.useEffect(() => {
const primaryContainer = fakeScrollerRef.current;
const secondaryContainer = scrollerXRef.current;
if (!primaryContainer || !secondaryContainer) return;

let isTransferred = false;

const handleWheel = (event: WheelEvent) => {
event.preventDefault();
const deltaX = event.deltaX;
const primaryScrollLeft = primaryContainer.scrollLeft;

if (primaryScrollLeft < offsetLeft || (primaryScrollLeft === offsetLeft && deltaX < 0)) {
// Scroll the primary container
console.log("test");
primaryContainer.scrollLeft = Math.max(0, Math.min(offsetLeft, primaryScrollLeft + deltaX));
if (isTransferred && deltaX < 0) {
isTransferred = false;
} else if (deltaX <= 0) {
secondaryContainer.scrollLeft += deltaX;
}
} else {
// Transfer scroll to secondary container
secondaryContainer.scrollLeft += deltaX;
primaryContainer.scrollLeft = offsetLeft; // Ensure primary stays at threshold
if (!isTransferred) {
isTransferred = true;
}
}
};

primaryContainer.addEventListener("wheel", handleWheel, { passive: false });
secondaryContainer.addEventListener("wheel", handleWheel, { passive: false });

return () => {
primaryContainer.removeEventListener("wheel", handleWheel);
secondaryContainer.removeEventListener("wheel", handleWheel);
};
}, []);

return (
<div
style={{
background: "red",
overflow: "auto",
height,
}}
ref={scrollerRef}>
ref={scrollerYRef}>
<style>{`
.hackygrid {
position: sticky;
Expand All @@ -68,31 +112,53 @@ export const AddColumns: React.FC<AddColumnsProps> = p => {
}
`}</style>
<header ref={headerRef}>
<h1 contentEditable style={{ margin: 0 }}>
This is the article title
</h1>
<h1 style={{ margin: 0 }}>This is the article title</h1>
<div style={{ margin: 0 }}>something something nav</div>
</header>

<div
ref={scrollerXRef}
style={{ marginBlock: 20, overflowX: "scroll", height: 400, width: "100%", position: "absolute" }}>
{" "}
This is the scroller
<div style={{ background: "red", width: "200%" }} />
</div>

<div
ref={fakeScrollerRef}
style={{
width: "100%",
marginBlock: 20,
overflowX: "scroll",
height: `${(rowCount + 1) * rowHeight + offsetBottom}px`,
paddingBottom: offsetBottom,
paddingInline: offsetLeft,
}}>
<DataEditor
{...defaultProps}
rowHeight={rowHeight}
headerHeight={rowHeight}
height={"500px"}
rowMarkers="number"
className="hackygrid"
getCellContent={getCellContent}
experimental={{ strict: true }}
columns={cols}
rows={rowCount}
scrollerRef={scrollerRef}
scrollOffsetTop={55.5}
scrollOffsetBottom={offsetBottom}
/>
<div
style={{
width: "200%",
marginBlock: 20,
overflowX: "scroll",
height: `${(rowCount + 1) * rowHeight + offsetBottom}px`,
paddingBottom: offsetBottom,
}}>
<DataEditor
{...defaultProps}
rowHeight={rowHeight}
headerHeight={rowHeight}
height={"500px"}
rowMarkers="number"
className="hackygrid"
getCellContent={getCellContent}
experimental={{ strict: true }}
columns={cols}
rows={rowCount}
scrollerYRef={scrollerYRef}
scrollerXRef={scrollerXRef}
scrollOffsetTop={55.5}
scrollOffsetBottom={offsetBottom}
/>
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export interface DataGridSearchProps extends Omit<ScrollingDataGridProps, "preli
*/
readonly onSearchValueChange?: (newVal: string) => void;
readonly searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
readonly scrollerRef?: React.RefObject<HTMLDivElement>;
readonly scrollerXRef?: React.RefObject<HTMLDivElement>;
readonly scrollerYRef?: React.RefObject<HTMLDivElement>;
}

const targetSearchTimeMS = 10;
Expand Down Expand Up @@ -547,7 +548,8 @@ const DataGridSearch: React.FunctionComponent<DataGridSearchProps> = p => {
smoothScrollX={p.smoothScrollX}
smoothScrollY={p.smoothScrollY}
resizeIndicator={p.resizeIndicator}
scrollerRef={p.scrollerRef}
scrollerXRef={p.scrollerXRef}
scrollerYRef={p.scrollerYRef}
scrollOffsetTop={p.scrollOffsetTop}
scrollOffsetBottom={p.scrollOffsetBottom}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ interface Props {
readonly kineticScrollPerfHack?: boolean;
readonly scrollRef?: React.MutableRefObject<HTMLDivElement | null>;
readonly update: (region: Rectangle & { paddingRight: number }) => void;
readonly scrollerRef?: React.RefObject<HTMLDivElement>;
readonly scrollerXRef?: React.RefObject<HTMLDivElement>;
readonly scrollerYRef?: React.RefObject<HTMLDivElement>;
readonly scrollOffsetTop?: number;
readonly scrollOffsetBottom?: number;
}
Expand Down Expand Up @@ -127,7 +128,8 @@ export const InfiniteScroller: React.FC<Props> = p => {
kineticScrollPerfHack = false,
scrollRef,
initialSize,
scrollerRef: explicitScrollerRef,
scrollerXRef: explicitScrollerXRef,
scrollerYRef: explicitScrollerYRef,
scrollOffsetTop,
scrollOffsetBottom,
} = p;
Expand All @@ -138,7 +140,8 @@ export const InfiniteScroller: React.FC<Props> = p => {

const offsetY = React.useRef(0);
const lastScrollY = React.useRef(0);
const scroller = React.useRef<HTMLDivElement | null>(explicitScrollerRef?.current ?? null);
const scrollerX = React.useRef<HTMLDivElement | null>(explicitScrollerXRef?.current ?? null);
const scrollerY = React.useRef<HTMLDivElement | null>(explicitScrollerYRef?.current ?? null);

const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio;

Expand All @@ -156,24 +159,28 @@ export const InfiniteScroller: React.FC<Props> = p => {

React.useLayoutEffect(() => {
if (!isIdle || hasTouches || lastScrollPosition.current.lockDirection === undefined) return;
const el = scroller.current;
if (el === null) return;
const elX = scrollerX.current;
const elY = scrollerY.current;
if (elX === null) return;
if (elY === null) return;
const [lx, ly] = lastScrollPosition.current.lockDirection;
if (lx !== undefined) {
el.scrollLeft = lx;
elX.scrollLeft = lx;
} else if (ly !== undefined) {
el.scrollTop = ly;
elY.scrollTop = ly;
}
lastScrollPosition.current.lockDirection = undefined;
}, [hasTouches, isIdle]);

const onScroll = React.useCallback(
(scrollLeft?: number, scrollTop?: number) => {
const el = scroller.current;
if (el === null) return;
const elY = scrollerY.current;
const elX = scrollerX.current;

scrollTop = scrollTop ?? el.scrollTop;
scrollLeft = scrollLeft ?? el.scrollLeft;
if (!elY || !elX) return;

scrollTop = scrollTop ?? elY?.scrollTop ?? 0;
scrollLeft = scrollLeft ?? elX?.scrollLeft ?? 0;
const lastScrollTop = lastScrollPosition.current.scrollTop;
const lastScrollLeft = lastScrollPosition.current.scrollLeft;

Expand All @@ -188,9 +195,9 @@ export const InfiniteScroller: React.FC<Props> = p => {
}

if (stickyBottom) {
const scrollRemaining = el.scrollHeight - scrollTop - el.clientHeight - stickyTop;
const scrollRemaining = elY.scrollHeight - scrollTop - elY.clientHeight - stickyTop;
if (scrollRemaining < stickyBottom) {
scrollTop = el.scrollHeight - stickyTop - el.clientHeight - stickyBottom;
scrollTop = elY.scrollHeight - stickyTop - elY.clientHeight - stickyBottom;
}
}

Expand All @@ -213,18 +220,18 @@ export const InfiniteScroller: React.FC<Props> = p => {
lastScrollPosition.current.scrollLeft = scrollLeft;
lastScrollPosition.current.scrollTop = scrollTop;

const cWidth = el.clientWidth;
const cHeight = el.clientHeight;
const cWidth = elX.clientWidth;
const cHeight = elY.clientHeight;

const newY = scrollTop;
const delta = lastScrollY.current - newY;
const scrollableHeight = el.scrollHeight - cHeight;
const scrollableHeight = elY.scrollHeight - cHeight;
lastScrollY.current = newY;

if (
scrollableHeight > 0 &&
(Math.abs(delta) > 2000 || newY === 0 || newY === scrollableHeight) &&
scrollHeight > el.scrollHeight + 5
scrollHeight > elY.scrollHeight + 5
) {
const prog = newY / scrollableHeight;
const recomputed = (scrollHeight - cHeight) * prog;
Expand Down Expand Up @@ -257,7 +264,7 @@ export const InfiniteScroller: React.FC<Props> = p => {
]
);

useKineticScroll(kineticScrollPerfHack && browserIsSafari.value, onScroll, scroller);
useKineticScroll(kineticScrollPerfHack && browserIsSafari.value, onScroll, { scrollerX, scrollerY });

const onScrollRef = React.useRef(onScroll);
onScrollRef.current = onScroll;
Expand All @@ -274,13 +281,15 @@ export const InfiniteScroller: React.FC<Props> = p => {

const setRefs = React.useCallback(
(_instance: HTMLDivElement | null) => {
const instance = explicitScrollerRef?.current ?? _instance;
scroller.current = instance;
const instanceOfSrollerX = explicitScrollerXRef?.current ?? _instance;
const instanceOfSrollerY = explicitScrollerYRef?.current ?? _instance;
scrollerX.current = instanceOfSrollerX;
scrollerY.current = instanceOfSrollerY;
if (scrollRef !== undefined) {
scrollRef.current = _instance;
}
},
[scrollRef, explicitScrollerRef]
[scrollRef, explicitScrollerXRef, explicitScrollerYRef]
);

let key = 0;
Expand All @@ -295,16 +304,20 @@ export const InfiniteScroller: React.FC<Props> = p => {
const { ref, width, height } = useResizeDetector<HTMLDivElement>(initialSize);

React.useEffect(() => {
const el = scroller.current;
if (!el) return;
const elX = scrollerX.current;
const elY = scrollerY.current;
if (!elX) return;
if (!elY) return;

const _onScroll = () => {
onScroll();
};

el.addEventListener("scroll", _onScroll);
elX.addEventListener("scroll", _onScroll);
elY.addEventListener("scroll", _onScroll);
return () => {
el.removeEventListener("scroll", _onScroll);
elX.removeEventListener("scroll", _onScroll);
elY.removeEventListener("scroll", _onScroll);
};
}, [onScroll]);

Expand Down Expand Up @@ -346,7 +359,7 @@ export const InfiniteScroller: React.FC<Props> = p => {
marginBottom: -40,
marginRight: paddingRight,
flexGrow: rightElementFill ? 1 : undefined,
right: rightElementSticky ? paddingRight ?? 0 : undefined,
right: rightElementSticky ? (paddingRight ?? 0) : undefined,
pointerEvents: "auto",
}}>
{rightElement}
Expand Down
Loading

0 comments on commit 81aeb2f

Please sign in to comment.