mirror of
https://github.com/docmost/docmost.git
synced 2026-08-30 18:36:26 +08:00
fix(base): enable grid cell editing on touch devices
Cells could only enter edit mode via double-click or a physical keyboard, so touch devices had no way to edit a cell. Treat a touch/pen tap as the edit gesture, distinguishing a tap from a scroll by movement and branching per pointer type so mouse double-click stays unchanged. Also reveal the row expand button on hover-less devices so the row detail view stays reachable.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback, useMemo } from "react";
|
import { memo, useCallback, useMemo, useRef } from "react";
|
||||||
import { Cell } from "@tanstack/react-table";
|
import { Cell } from "@tanstack/react-table";
|
||||||
import { Popover, Tooltip } from "@mantine/core";
|
import { Popover, Tooltip } from "@mantine/core";
|
||||||
import { IconArrowsDiagonal } from "@tabler/icons-react";
|
import { IconArrowsDiagonal } from "@tabler/icons-react";
|
||||||
@@ -24,6 +24,8 @@ import { useRowExpand } from "@/ee/base/context/row-expand";
|
|||||||
import { RowNumberCell } from "./row-number-cell";
|
import { RowNumberCell } from "./row-number-cell";
|
||||||
import classes from "@/ee/base/styles/grid.module.css";
|
import classes from "@/ee/base/styles/grid.module.css";
|
||||||
|
|
||||||
|
const TOUCH_TAP_SLOP_PX = 10;
|
||||||
|
|
||||||
type GridCellProps = {
|
type GridCellProps = {
|
||||||
cell: Cell<IBaseRow, unknown>;
|
cell: Cell<IBaseRow, unknown>;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
@@ -72,6 +74,9 @@ export const GridCell = memo(function GridCell({
|
|||||||
editingCell?.propertyId === property?.id &&
|
editingCell?.propertyId === property?.id &&
|
||||||
(editable || property?.type === "file");
|
(editable || property?.type === "file");
|
||||||
|
|
||||||
|
const tapStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||||
|
const suppressClickRef = useRef(false);
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(() => {
|
const handleDoubleClick = useCallback(() => {
|
||||||
if (!property || isRowNumber) return;
|
if (!property || isRowNumber) return;
|
||||||
if (property.type === "checkbox") return;
|
if (property.type === "checkbox") return;
|
||||||
@@ -102,6 +107,10 @@ export const GridCell = memo(function GridCell({
|
|||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!property) return;
|
if (!property) return;
|
||||||
|
if (suppressClickRef.current) {
|
||||||
|
suppressClickRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
setFocusedCell({ rowId, propertyId: property.id });
|
setFocusedCell({ rowId, propertyId: property.id });
|
||||||
(e.currentTarget.closest('[role="grid"]') as HTMLElement | null)?.focus({
|
(e.currentTarget.closest('[role="grid"]') as HTMLElement | null)?.focus({
|
||||||
preventScroll: true,
|
preventScroll: true,
|
||||||
@@ -110,6 +119,38 @@ export const GridCell = memo(function GridCell({
|
|||||||
[property, rowId, setFocusedCell],
|
[property, rowId, setFocusedCell],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handlePointerDown = useCallback(
|
||||||
|
(e: React.PointerEvent<HTMLDivElement>) => {
|
||||||
|
if (e.pointerType === "mouse") return;
|
||||||
|
suppressClickRef.current = false;
|
||||||
|
tapStartRef.current = { x: e.clientX, y: e.clientY };
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePointerUp = useCallback(
|
||||||
|
(e: React.PointerEvent<HTMLDivElement>) => {
|
||||||
|
if (e.pointerType === "mouse") return;
|
||||||
|
const start = tapStartRef.current;
|
||||||
|
tapStartRef.current = null;
|
||||||
|
if (!start) return;
|
||||||
|
if (
|
||||||
|
Math.abs(e.clientX - start.x) > TOUCH_TAP_SLOP_PX ||
|
||||||
|
Math.abs(e.clientY - start.y) > TOUCH_TAP_SLOP_PX
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((e.target as HTMLElement).closest("button, a, input")) return;
|
||||||
|
suppressClickRef.current = true;
|
||||||
|
handleDoubleClick();
|
||||||
|
},
|
||||||
|
[handleDoubleClick],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePointerCancel = useCallback(() => {
|
||||||
|
tapStartRef.current = null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const cellReadOnly = property
|
const cellReadOnly = property
|
||||||
? readOnly || isSystemPropertyType(property.type)
|
? readOnly || isSystemPropertyType(property.type)
|
||||||
: false;
|
: false;
|
||||||
@@ -200,6 +241,9 @@ export const GridCell = memo(function GridCell({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerUp={handlePointerUp}
|
||||||
|
onPointerCancel={handlePointerCancel}
|
||||||
>
|
>
|
||||||
<CellComponent
|
<CellComponent
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -479,6 +479,13 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
.rowExpandButton {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rowExpandButton:hover {
|
.rowExpandButton:hover {
|
||||||
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
||||||
color: light-dark(var(--mantine-color-blue-6), var(--mantine-color-blue-4));
|
color: light-dark(var(--mantine-color-blue-6), var(--mantine-color-blue-4));
|
||||||
|
|||||||
Reference in New Issue
Block a user