fix(base): sync header/body horizontal scroll on header focus; expand row via Space, drop expander from tab order

This commit is contained in:
Philipinho
2026-06-15 04:08:52 +01:00
parent cc951ae883
commit 8fc538386d
4 changed files with 31 additions and 5 deletions
@@ -181,6 +181,7 @@ export const GridCell = memo(function GridCell({
<Tooltip label={t("Expand")} position="bottom" openDelay={400}> <Tooltip label={t("Expand")} position="bottom" openDelay={400}>
<button <button
type="button" type="button"
tabIndex={-1}
className={classes.rowExpandButton} className={classes.rowExpandButton}
onClick={() => onExpandRow(rowId)} onClick={() => onExpandRow(rowId)}
onDoubleClick={(e) => e.stopPropagation()} onDoubleClick={(e) => e.stopPropagation()}
@@ -37,6 +37,7 @@ import { AddRowButton } from "./add-row-button";
import { GridGhostRows } from "./grid-ghost-rows"; import { GridGhostRows } from "./grid-ghost-rows";
import { SelectionActionBar } from "./selection-action-bar"; import { SelectionActionBar } from "./selection-action-bar";
import { useBaseEditable } from "@/ee/base/context/base-editable"; import { useBaseEditable } from "@/ee/base/context/base-editable";
import { useRowExpand } from "@/ee/base/context/row-expand";
import { GridRowOrderProvider } from "@/ee/base/context/grid-row-order"; import { GridRowOrderProvider } from "@/ee/base/context/grid-row-order";
import classes from "@/ee/base/styles/grid.module.css"; import classes from "@/ee/base/styles/grid.module.css";
@@ -120,6 +121,7 @@ export function GridContainer({
rowIdsRef.current = rowIds; rowIdsRef.current = rowIds;
const getOrderedRowIds = useCallback(() => rowIdsRef.current, []); const getOrderedRowIds = useCallback(() => rowIdsRef.current, []);
const editable = useBaseEditable(); const editable = useBaseEditable();
const onExpandRow = useRowExpand();
const [editingCell, setEditingCell] = useAtom(editingCellAtomFamily(pageId)) as unknown as [EditingCell, (val: EditingCell) => void]; const [editingCell, setEditingCell] = useAtom(editingCellAtomFamily(pageId)) as unknown as [EditingCell, (val: EditingCell) => void];
const editingCellRef = useRef(editingCell); const editingCellRef = useRef(editingCell);
@@ -334,6 +336,13 @@ export function GridContainer({
[toggleRow], [toggleRow],
); );
const expandRow = useCallback(
(rowId: string) => {
onExpandRow?.(rowId);
},
[onExpandRow],
);
const prevEditingRef = useRef(editingCell); const prevEditingRef = useRef(editingCell);
useEffect(() => { useEffect(() => {
const prev = prevEditingRef.current; const prev = prevEditingRef.current;
@@ -385,6 +394,7 @@ export function GridContainer({
clearSelection, clearSelection,
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow,
}); });
const activeCell = editingCell ?? focusedCell; const activeCell = editingCell ?? focusedCell;
@@ -25,6 +25,7 @@ type UseGridKeyboardNavOptions = {
clearSelection: () => void; clearSelection: () => void;
deleteSelected: () => void | Promise<void>; deleteSelected: () => void | Promise<void>;
toggleRowSelection: (rowId: string) => void; toggleRowSelection: (rowId: string) => void;
expandRow: (rowId: string) => void;
}; };
const isPrintableKey = (e: KeyboardEvent) => const isPrintableKey = (e: KeyboardEvent) =>
@@ -52,6 +53,7 @@ export function useGridKeyboardNav({
clearSelection, clearSelection,
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow,
}: UseGridKeyboardNavOptions) { }: UseGridKeyboardNavOptions) {
const getColIds = useCallback( const getColIds = useCallback(
() => () =>
@@ -244,12 +246,15 @@ export function useGridKeyboardNav({
} }
break; break;
default: { default: {
if (e.key === " " && focusedCell.propertyId === "__row_number") { if (e.key === " ") {
e.preventDefault(); e.preventDefault();
toggleRowSelection(focusedCell.rowId); if (focusedCell.propertyId === "__row_number") {
} else if (e.key === " " && propertyType(focusedCell.propertyId) === "checkbox") { toggleRowSelection(focusedCell.rowId);
e.preventDefault(); } else if (propertyType(focusedCell.propertyId) === "checkbox") {
openEditor(focusedCell); openEditor(focusedCell);
} else {
expandRow(focusedCell.rowId);
}
} else if (isPrintableKey(e)) { } else if (isPrintableKey(e)) {
e.preventDefault(); e.preventDefault();
beginTypeToEdit(focusedCell, e.key); beginTypeToEdit(focusedCell, e.key);
@@ -275,6 +280,7 @@ export function useGridKeyboardNav({
clearSelection, clearSelection,
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow,
], ],
); );
@@ -38,7 +38,15 @@ export function useHorizontalScrollSync<
body.scrollLeft += e.deltaY; body.scrollLeft += e.deltaY;
}; };
const onHeaderScroll = () => {
if (rafId !== 0) return;
if (body.scrollLeft !== header.scrollLeft) {
body.scrollLeft = header.scrollLeft;
}
};
body.addEventListener("scroll", onBodyScroll, { passive: true }); body.addEventListener("scroll", onBodyScroll, { passive: true });
header.addEventListener("scroll", onHeaderScroll, { passive: true });
header.addEventListener("wheel", onHeaderWheel, { passive: false }); header.addEventListener("wheel", onHeaderWheel, { passive: false });
// Initial sync in case the body is already scrolled when the hook mounts. // Initial sync in case the body is already scrolled when the hook mounts.
@@ -46,6 +54,7 @@ export function useHorizontalScrollSync<
return () => { return () => {
body.removeEventListener("scroll", onBodyScroll); body.removeEventListener("scroll", onBodyScroll);
header.removeEventListener("scroll", onHeaderScroll);
header.removeEventListener("wheel", onHeaderWheel); header.removeEventListener("wheel", onHeaderWheel);
if (rafId !== 0) cancelAnimationFrame(rafId); if (rafId !== 0) cancelAnimationFrame(rafId);
}; };