reactjsmaterial-uimui-x-data-grid

Tabbing inside of MUI Data-Grid dialog closes it


I have a MUI Data Grid where the user is allowed to cell edit values. One of the cells render a MUI Dialog via renderEditCell. In that dialog there are several input fields (MUI TextField) which I want to be able to tab through.

My problem now is, that as soon as I tab to the next field, the dialog is closed because the next cell of the data grid is focused.

I've tried to ignore the tab key in onCellEditStop with event.defaultMuiPrevent = true. With that, the dialog no longer closes, but the focus doesn't switch to the next input field. As far as I understood, I think this is because the event is first handled in the parent and thus the default is also prevented within the dialog.

I have a minimal reproducible example here (relevant code in onCellEditStop is commented out).

How can I fix this? Is there a different approach?


Solution

  • You can attach onKeyDown event on Dialog component and check if pressed key code is Tab then prevent the event bubbling up to the parent.

    onKeyDown={(e) => {
     if (e.code === "Tab") {
       e.stopPropagation();
     }
    }}
    

    Here's the complete code:

    CustomField.tsx

    import Dialog from "@mui/material/Dialog";
    import DialogContent from "@mui/material/DialogContent";
    import TextField from "@mui/material/TextField";
    import { useGridApiContext } from "@mui/x-data-grid";
    import React from "react";
    
    export default function CustomField({ id, field, row }) {
      const [open, setOpen] = React.useState(true);
      const apiRef = useGridApiContext();
    
      const handleClose = () => {
        setOpen(false);
        apiRef.current.stopCellEditMode({ id, field });
      };
    
      return (
        <Dialog
          open={open}
          onClose={handleClose}
          fullWidth
          maxWidth="md"
          onKeyDown={(e) => {
            if (e.code === "Tab") {
              e.stopPropagation();
            }
          }}
        >
          <DialogContent>
            <TextField
              fullWidth
              label="Min"
              name="min"
              size="small"
              sx={{ m: 1 }}
              type="number"
              defaultValue={row.validationDef?.min || ""}
            />
    
            <TextField
              fullWidth
              label="Max"
              name="max"
              size="small"
              sx={{ m: 1 }}
              type="number"
            />
          </DialogContent>
        </Dialog>
      );
    }
    

    App.tsx

    import {
      DataGrid,
      GridColDef,
      GridRenderEditCellParams,
    } from "@mui/x-data-grid";
    
    import Alert from "@mui/material/Alert";
    import CustomField from "./CustomField";
    
    export default function App() {
      const columns: GridColDef[] = [
        {
          field: "attribute",
          headerName: "Attribute",
          editable: true,
        },
        {
          field: "customField",
          headerName: "Dialog Field",
          flex: 1.5,
          renderEditCell: (params: GridRenderEditCellParams) => (
            <CustomField {...params} />
          ),
          editable: true,
        },
        {
          field: "type",
          type: "singleSelect",
          headerName: "Type",
          editable: true,
          valueOptions: [
            "COMPLEX",
            "BOOLEAN",
            "STRING",
            "NUMBER",
            "DATE",
            "DATETIME",
          ],
        },
      ];
    
      const rows = [
        {
          id: 1,
          attribute: "a name",
          customField: "double click me",
          type: "COMPLEX",
        },
      ];
    
      return (
        <div>
          <Alert severity="info">
            <ul>
              <li>Double click on the value in the "Dialog Field" column</li>
              <li>Click into the first input field</li>
              <li>Try to tab to the next input field</li>
            </ul>
          </Alert>
          <DataGrid
            rows={rows}
            columns={columns}
            rowHeight={40}
            hideFooter={true}
          />
        </div>
      );
    }