mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 01:07:08 +08:00
feat(base): cell focus ring, click-to-focus, and gridcell ARIA
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
import { memo, useCallback } from "react";
|
import { memo, useCallback, useMemo } 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";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom, useAtomValue, useSetAtom, type PrimitiveAtom } from "jotai";
|
||||||
import { IBaseRow, EditingCell } from "@/ee/base/types/base.types";
|
import { selectAtom } from "jotai/utils";
|
||||||
|
import { IBaseRow, EditingCell, FocusedCell } from "@/ee/base/types/base.types";
|
||||||
import {
|
import {
|
||||||
editingCellAtomFamily,
|
editingCellAtomFamily,
|
||||||
|
focusedCellAtomFamily,
|
||||||
activeFormulaEditorAtomFamily,
|
activeFormulaEditorAtomFamily,
|
||||||
FormulaEditorTarget,
|
FormulaEditorTarget,
|
||||||
} from "@/ee/base/atoms/base-atoms";
|
} from "@/ee/base/atoms/base-atoms";
|
||||||
@@ -24,6 +26,7 @@ import classes from "@/ee/base/styles/grid.module.css";
|
|||||||
type GridCellProps = {
|
type GridCellProps = {
|
||||||
cell: Cell<IBaseRow, unknown>;
|
cell: Cell<IBaseRow, unknown>;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
|
colIndex?: number;
|
||||||
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
};
|
};
|
||||||
@@ -31,6 +34,7 @@ type GridCellProps = {
|
|||||||
export const GridCell = memo(function GridCell({
|
export const GridCell = memo(function GridCell({
|
||||||
cell,
|
cell,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
|
colIndex,
|
||||||
onCellUpdate,
|
onCellUpdate,
|
||||||
pageId,
|
pageId,
|
||||||
}: GridCellProps) {
|
}: GridCellProps) {
|
||||||
@@ -44,6 +48,18 @@ export const GridCell = memo(function GridCell({
|
|||||||
activeFormulaEditorAtomFamily(pageId),
|
activeFormulaEditorAtomFamily(pageId),
|
||||||
) as unknown as [FormulaEditorTarget, (val: FormulaEditorTarget) => void];
|
) as unknown as [FormulaEditorTarget, (val: FormulaEditorTarget) => void];
|
||||||
|
|
||||||
|
const setFocusedCell = useSetAtom(focusedCellAtomFamily(pageId) as PrimitiveAtom<FocusedCell>);
|
||||||
|
const isFocused = useAtomValue(
|
||||||
|
useMemo(
|
||||||
|
() =>
|
||||||
|
selectAtom(
|
||||||
|
focusedCellAtomFamily(pageId),
|
||||||
|
(fc) => fc?.rowId === cell.row.id && fc?.propertyId === property?.id,
|
||||||
|
),
|
||||||
|
[pageId, cell.row.id, property?.id],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const editable = useBaseEditable();
|
const editable = useBaseEditable();
|
||||||
const readOnly = !editable;
|
const readOnly = !editable;
|
||||||
@@ -74,6 +90,21 @@ export const GridCell = memo(function GridCell({
|
|||||||
setEditingCell({ rowId, propertyId: property.id });
|
setEditingCell({ rowId, propertyId: property.id });
|
||||||
}, [property, isRowNumber, rowId, readOnly, setEditingCell, setActiveFormulaEditor]);
|
}, [property, isRowNumber, rowId, readOnly, setEditingCell, setActiveFormulaEditor]);
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!property) return;
|
||||||
|
setFocusedCell({ rowId, propertyId: property.id });
|
||||||
|
(e.currentTarget.closest('[role="grid"]') as HTMLElement | null)?.focus({
|
||||||
|
preventScroll: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[property, rowId, setFocusedCell],
|
||||||
|
);
|
||||||
|
|
||||||
|
const cellReadOnly = property
|
||||||
|
? readOnly || isSystemPropertyType(property.type)
|
||||||
|
: false;
|
||||||
|
|
||||||
const closeFormulaEditor = useCallback(
|
const closeFormulaEditor = useCallback(
|
||||||
() => setActiveFormulaEditor(null),
|
() => setActiveFormulaEditor(null),
|
||||||
[setActiveFormulaEditor],
|
[setActiveFormulaEditor],
|
||||||
@@ -122,12 +153,17 @@ export const GridCell = memo(function GridCell({
|
|||||||
|
|
||||||
const cellInner = (
|
const cellInner = (
|
||||||
<div
|
<div
|
||||||
className={`${classes.cell} ${isPinned ? classes.cellPinned : ""} ${isEditing ? classes.cellEditing : ""} ${property.isPrimary ? classes.primaryCell : ""}`}
|
id={`base-cell-${rowId}-${property.id}`}
|
||||||
|
role="gridcell"
|
||||||
|
aria-colindex={colIndex != null ? colIndex + 1 : undefined}
|
||||||
|
aria-readonly={cellReadOnly || undefined}
|
||||||
|
className={`${classes.cell} ${isPinned ? classes.cellPinned : ""} ${isEditing ? classes.cellEditing : ""} ${isFocused && !isEditing ? classes.cellFocused : ""} ${property.isPrimary ? classes.primaryCell : ""}`}
|
||||||
style={
|
style={
|
||||||
isPinned
|
isPinned
|
||||||
? ({ "--pin-offset": `${pinOffset}px` } as React.CSSProperties)
|
? ({ "--pin-offset": `${pinOffset}px` } as React.CSSProperties)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
onClick={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
>
|
>
|
||||||
<CellComponent
|
<CellComponent
|
||||||
@@ -209,6 +245,7 @@ gridCellPropsEqual);
|
|||||||
function gridCellPropsEqual(prev: GridCellProps, next: GridCellProps) {
|
function gridCellPropsEqual(prev: GridCellProps, next: GridCellProps) {
|
||||||
if (
|
if (
|
||||||
prev.rowIndex !== next.rowIndex ||
|
prev.rowIndex !== next.rowIndex ||
|
||||||
|
prev.colIndex !== next.colIndex ||
|
||||||
prev.pageId !== next.pageId ||
|
prev.pageId !== next.pageId ||
|
||||||
prev.onCellUpdate !== next.onCellUpdate
|
prev.onCellUpdate !== next.onCellUpdate
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user