Skip to content

Commit

Permalink
Allow scrolling the table horizontally by using the mouse wheel on th…
Browse files Browse the repository at this point in the history
…e table header.
  • Loading branch information
TPReal committed Oct 30, 2023
1 parent 12c089f commit 6f81d3f
Showing 1 changed file with 77 additions and 4 deletions.
81 changes: 77 additions & 4 deletions resources/js/components/ui/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,28 @@ import {
getPaginationRowModel,
getSortedRowModel,
} from "@tanstack/solid-table";
import {LangEntryFunc, LangPrefixFunc, createTranslationsFromPrefix, cx} from "components/utils";
import {Accessor, For, Index, JSX, Show, Signal, VoidProps, createEffect, createSignal, mergeProps, on} from "solid-js";
import {
LangEntryFunc,
LangPrefixFunc,
createTranslationsFromPrefix,
currentTime,
cx,
debouncedAccessor,
} from "components/utils";
import {
Accessor,
For,
Index,
JSX,
Show,
Signal,
VoidProps,
createEffect,
createMemo,
createSignal,
mergeProps,
on,
} from "solid-js";
import {Dynamic} from "solid-js/web";
import {TableContextProvider, getHeaders, useTableCells} from ".";
import {BigSpinner} from "../Spinner";
Expand Down Expand Up @@ -123,12 +143,54 @@ export const Table = <T,>(allProps: VoidProps<Props<T>>): JSX.Element => {
.getVisibleLeafColumns()
.map((c) => (!c.getCanResize() && c.getSize() === AUTO_SIZE_COLUMN_DEFS.size ? "auto" : `${c.getSize()}px`))
.join(" ");
// Implement horizontal scrolling on mouse wheel on the table header.
// The simplest implementation of calling scrollBy in the onWheel handler does not work well if
// smooth scrolling is used. That's why this code tracks the desired position in a signal and scrolls
// to it when no scrolling is taking place at the moment.
const [scrollingWrapper, setScrollingWrapper] = createSignal<HTMLDivElement>();
const [lastScrollTimestamp, setLastScrollTimestamp] = createSignal(0);
// Whether the table is currently scrolling. This is true after setting lastScrollTimestamp to 0
// in onScrollEnd, but also after enough time is elapsed since the last onScroll event, because in some
// situations the onScrollEnd event is not reliable, and we don't want to get stuck thinking the table
// is still scrolling when it's not.
const isScrolling = createMemo(() => currentTime().toMillis() - lastScrollTimestamp() < 100);
const [desiredScrollX, setDesiredScrollX] = createSignal<number>();
createEffect(
on(
[
scrollingWrapper,
isScrolling,
// Allow multiple steps to accummulate before this is triggered. This improves smoothness.
// eslint-disable-next-line solid/reactivity
debouncedAccessor(desiredScrollX, {
timeMs: 100,
outputImmediately: (x) => x === undefined,
}),
],
([scrWrapper, isScrolling]) => {
if (scrWrapper && !isScrolling) {
// Use the most up-to-date value of desiredScrollX, not the debounced one.
const desiredX = desiredScrollX();
if (desiredX !== undefined && desiredX !== scrWrapper.scrollLeft) {
scrWrapper.scrollTo({left: desiredX, behavior: "smooth"});
} else {
setDesiredScrollX(undefined);
}
}
},
),
);
return (
<TableContextProvider table={props.table}>
<Show when={!props.isLoading} fallback={<BigSpinner />}>
<div class={cx(s.tableContainer, s[props.mode])}>
<Show when={props.aboveTable?.()}>{(aboveTable) => <div class={s.aboveTable}>{aboveTable()}</div>}</Show>
<div class={s.scrollingWrapper}>
<div
ref={setScrollingWrapper}
class={s.scrollingWrapper}
onScroll={() => setLastScrollTimestamp(Date.now())}
onScrollEnd={() => setLastScrollTimestamp(0)}
>
<div ref={scrollToTopPoint} class={s.tableBg}>
<div
class={cx(s.table, {[s.dimmed!]: props.isDimmed})}
Expand All @@ -139,7 +201,18 @@ export const Table = <T,>(allProps: VoidProps<Props<T>>): JSX.Element => {
{({header, column}) => (
<Show when={header()}>
{(header) => (
<div class={s.cell}>
<div
class={s.cell}
onWheel={(e) => {
const scrWrapper = scrollingWrapper();
if (scrWrapper && e.deltaY) {
setDesiredScrollX((l = scrWrapper.scrollLeft) =>
Math.min(Math.max(l + e.deltaY, 0), scrWrapper.scrollWidth - scrWrapper.clientWidth),
);
e.preventDefault();
}
}}
>
<Show when={!header().isPlaceholder}>
<CellRenderer component={column.columnDef.header} props={header().getContext()} />
</Show>
Expand Down

0 comments on commit 6f81d3f

Please sign in to comment.