feat(base): insert row below via Shift+Enter on the primary cell

This commit is contained in:
Philipinho
2026-06-16 13:22:54 +01:00
parent c43ac7fc8c
commit 2e80fc457c
5 changed files with 75 additions and 31 deletions
@@ -18,7 +18,7 @@ type BaseTableProps = {
isFetchingNextPage: boolean; isFetchingNextPage: boolean;
onFetchNextPage: () => void; onFetchNextPage: () => void;
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
onAddRow: () => void; onAddRow: (afterRowId?: string, focusPropertyId?: string) => void;
onColumnReorder: (columnId: string, finishIndex: number) => void; onColumnReorder: (columnId: string, finishIndex: number) => void;
onResizeEnd: () => void; onResizeEnd: () => void;
onRowReorder: ( onRowReorder: (
@@ -12,6 +12,7 @@ import {
FilterGroup, FilterGroup,
ViewSortConfig, ViewSortConfig,
EditingCell, EditingCell,
FocusedCell,
IBaseProperty, IBaseProperty,
} from "@/ee/base/types/base.types"; } from "@/ee/base/types/base.types";
import { import {
@@ -25,6 +26,7 @@ import { useUpdateViewMutation } from "@/ee/base/queries/base-view-query";
import { import {
activeViewIdAtomFamily, activeViewIdAtomFamily,
editingCellAtomFamily, editingCellAtomFamily,
focusedCellAtomFamily,
} from "@/ee/base/atoms/base-atoms"; } from "@/ee/base/atoms/base-atoms";
import { useBaseTable } from "@/ee/base/hooks/use-base-table"; import { useBaseTable } from "@/ee/base/hooks/use-base-table";
import { isSystemPropertyType } from "@/ee/base/property-types/property-type.registry"; import { isSystemPropertyType } from "@/ee/base/property-types/property-type.registry";
@@ -89,6 +91,10 @@ export function BaseView({ pageId, embedded, editable = true, titleSlot }: BaseV
editingCellAtomFamily(pageId), editingCellAtomFamily(pageId),
) as unknown as [EditingCell, (val: EditingCell) => void]; ) as unknown as [EditingCell, (val: EditingCell) => void];
const [, setFocusedCell] = useAtom(
focusedCellAtomFamily(pageId),
) as unknown as [FocusedCell, (val: FocusedCell) => void];
const views = useMemo( const views = useMemo(
() => () =>
[...(base?.views ?? [])].sort((a, b) => [...(base?.views ?? [])].sort((a, b) =>
@@ -221,33 +227,42 @@ export function BaseView({ pageId, embedded, editable = true, titleSlot }: BaseV
[editable, pageId, updateRow], [editable, pageId, updateRow],
); );
const handleAddRow = useCallback(() => { const handleAddRow = useCallback(
if (!editable) return; (afterRowId?: string, focusPropertyId?: string) => {
createRowMutation.mutate( if (!editable) return;
{ pageId }, createRowMutation.mutate(
{ { pageId, ...(afterRowId ? { afterRowId } : {}) },
onSuccess: (newRow) => { {
const firstEditable = table.getVisibleLeafColumns().find((col) => { onSuccess: (newRow) => {
if (col.id === "__row_number") return false; let propertyId = focusPropertyId;
const prop = col.columnDef.meta?.property as if (!propertyId) {
| IBaseProperty const firstEditable = table.getVisibleLeafColumns().find((col) => {
| undefined; if (col.id === "__row_number") return false;
return ( const prop = col.columnDef.meta?.property as
!!prop && | IBaseProperty
prop.type !== "checkbox" && | undefined;
!isSystemPropertyType(prop.type) return (
); !!prop &&
}); prop.type !== "checkbox" &&
const propertyId = ( !isSystemPropertyType(prop.type)
firstEditable?.columnDef.meta?.property as IBaseProperty | undefined );
)?.id; });
if (propertyId) { propertyId = (
setEditingCell({ rowId: newRow.id, propertyId }); firstEditable?.columnDef.meta?.property as
} | IBaseProperty
| undefined
)?.id;
}
if (propertyId) {
setEditingCell({ rowId: newRow.id, propertyId });
setFocusedCell({ rowId: newRow.id, propertyId });
}
},
}, },
}, );
); },
}, [editable, pageId, createRowMutation, table, setEditingCell]); [editable, pageId, createRowMutation, table, setEditingCell, setFocusedCell],
);
const handleViewChange = useCallback( const handleViewChange = useCallback(
(viewId: string) => { (viewId: string) => {
@@ -67,7 +67,7 @@ type GridContainerProps = {
table: Table<IBaseRow>; table: Table<IBaseRow>;
properties: IBaseProperty[]; properties: IBaseProperty[];
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
onAddRow?: () => void; onAddRow?: (afterRowId?: string, focusPropertyId?: string) => void;
pageId: string; pageId: string;
onColumnReorder?: (columnId: string, finishIndex: number) => void; onColumnReorder?: (columnId: string, finishIndex: number) => void;
onResizeEnd?: () => void; onResizeEnd?: () => void;
@@ -378,6 +378,13 @@ export function GridContainer({
[table, setFocusedCell], [table, setFocusedCell],
); );
const handleAddRowBelow = useCallback(
(afterRowId: string, focusPropertyId: string) => {
onAddRow?.(afterRowId, focusPropertyId);
},
[onAddRow],
);
useGridKeyboardNav({ useGridKeyboardNav({
table, table,
properties, properties,
@@ -395,6 +402,7 @@ export function GridContainer({
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow, expandRow,
addRow: handleAddRowBelow,
}); });
const activeCell = editingCell ?? focusedCell; const activeCell = editingCell ?? focusedCell;
@@ -21,7 +21,7 @@ type ViewRendererProps = {
isFetchingNextPage: boolean; isFetchingNextPage: boolean;
onFetchNextPage: () => void; onFetchNextPage: () => void;
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
onAddRow: () => void; onAddRow: (afterRowId?: string, focusPropertyId?: string) => void;
onColumnReorder: (columnId: string, finishIndex: number) => void; onColumnReorder: (columnId: string, finishIndex: number) => void;
onResizeEnd: () => void; onResizeEnd: () => void;
onRowReorder: ( onRowReorder: (
@@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { Table } from "@tanstack/react-table"; import { Table } from "@tanstack/react-table";
import { import {
IBaseRow, IBaseRow,
@@ -26,6 +26,7 @@ type UseGridKeyboardNavOptions = {
deleteSelected: () => void | Promise<void>; deleteSelected: () => void | Promise<void>;
toggleRowSelection: (rowId: string) => void; toggleRowSelection: (rowId: string) => void;
expandRow: (rowId: string) => void; expandRow: (rowId: string) => void;
addRow: (afterRowId: string, focusPropertyId: string) => void;
}; };
const isPrintableKey = (e: KeyboardEvent) => const isPrintableKey = (e: KeyboardEvent) =>
@@ -54,6 +55,7 @@ export function useGridKeyboardNav({
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow, expandRow,
addRow,
}: UseGridKeyboardNavOptions) { }: UseGridKeyboardNavOptions) {
const getColIds = useCallback( const getColIds = useCallback(
() => () =>
@@ -79,6 +81,11 @@ export function useGridKeyboardNav({
[properties], [properties],
); );
const primaryPropertyId = useMemo(
() => properties.find((p) => p.isPrimary)?.id,
[properties],
);
const goEditing = useCallback( const goEditing = useCallback(
(next: CellCoord) => { (next: CellCoord) => {
(document.activeElement as HTMLElement | null)?.blur(); (document.activeElement as HTMLElement | null)?.blur();
@@ -142,6 +149,12 @@ export function useGridKeyboardNav({
} }
case "Enter": { case "Enter": {
e.preventDefault(); e.preventDefault();
if (e.shiftKey && editingCell.propertyId === primaryPropertyId) {
(document.activeElement as HTMLElement | null)?.blur();
setEditingCell(null);
addRow(editingCell.rowId, editingCell.propertyId);
break;
}
const next = computeNextCell( const next = computeNextCell(
getRowIds(), getRowIds(),
getColIds(), getColIds(),
@@ -241,7 +254,13 @@ export function useGridKeyboardNav({
case "Enter": case "Enter":
case "F2": case "F2":
e.preventDefault(); e.preventDefault();
if (focusedCell.propertyId === "__row_number") { if (
e.key === "Enter" &&
e.shiftKey &&
focusedCell.propertyId === primaryPropertyId
) {
addRow(focusedCell.rowId, focusedCell.propertyId);
} else if (focusedCell.propertyId === "__row_number") {
toggleRowSelection(focusedCell.rowId); toggleRowSelection(focusedCell.rowId);
} else { } else {
openEditor(focusedCell); openEditor(focusedCell);
@@ -284,6 +303,8 @@ export function useGridKeyboardNav({
deleteSelected, deleteSelected,
toggleRowSelection, toggleRowSelection,
expandRow, expandRow,
primaryPropertyId,
addRow,
], ],
); );