2

I am trying to create a draggable Material UI dialog which includes in its contents an Autocomplete component. The result should look something like the following:

autocomplete dialog initial positioning

The desktop experience here is that when one drags the dialog (by grabbing the title area), the text field loses focus, and so the options Popper immediately disappears. However, the touch experience (tested iPhone 6 and iPad 7) is broken in that the text field remains focused while dragging, and so the popper doesn't disappear but also doesn't drag with the dialog:

autocomplete dialog dragged and broken

Notice as a further weirdness that the text cursor (circled) lines up with the text field every time the dialog is grabbed but then does not move while the dragging is happening (this bug exhibits even without Autocomplete, so maybe beyond the scope of this question, but thought I'd mention for completeness).

I think I understand why this is happening: The popper is a child of body in the DOM and not the draggable Paper element, so it isn't being targeted the way one would naively hope. However, the fact remains that it is happening, and I would like a way to fix it, if possible.

Note that a bug has been filed here.

Code below:

import React from "react";
import {
  Dialog,
  DialogContent,
  DialogContentText,
  DialogTitle,
  DialogActions,
  Button,
  Paper,
  TextField,
} from "@material-ui/core";
import Autocomplete from "@material-ui/lab/Autocomplete";
import Draggable from "react-draggable";

function ComboBox() {
  return (
    <Autocomplete
      id="combo-box-demo"
      options={top3Films}
      getOptionLabel={(option) => option.title}
      style={{ width: 300 }}
      renderInput={(params) => (
        <TextField {...params} label="Combo box" variant="outlined" />
      )}
    />
  );
}

const top3Films = [
  { title: "The Shawshank Redemption", year: 1994 },
  { title: "The Godfather", year: 1972 },
  { title: "The Godfather: Part II", year: 1974 },
];

const titleId = "draggable-title";

function DraggablePaper(props) {
  // This is to quiet strict mode warnings about findDOMNode usage. See
  // https://stackoverflow.com/a/63603903/12162258 for details
  const nodeRef = React.useRef(null);

  return (
    <Draggable
      handle={`#${titleId}`}
      cancel={'[class*="MuiDialogContent-root"]'}
      nodeRef={nodeRef}
    >
      <Paper ref={nodeRef} {...props} />
    </Draggable>
  );
}

function App() {
  return (
    <div>
      <Dialog open={true} PaperComponent={DraggablePaper}>
        <DialogTitle style={{ cursor: "move" }} id={titleId}>
          Title
        </DialogTitle>
        <DialogContent>
          <DialogContentText>Content text</DialogContentText>
          <ComboBox />
        </DialogContent>
        <DialogActions>
          <Button>Submit</Button>
          <Button>Cancel</Button>
        </DialogActions>
      </Dialog>
    </div>
  );
}

export default App;

thisisrandy
  • 2,660
  • 2
  • 12
  • 25

1 Answers1

0

While I don't think it should ideally be necessary, I have found a solution. react-draggable accepts a callback prop onStart, which, using a react ref, we can leverage to blur the input whenever the dialog starts being dragged, effectively mimicking the desktop experience. Modified portions of the question code with comments at each modification follow:

function App() {
  // create a ref to track the input DOM node
  const inputRef = useRef();
  // create a callback to call on drag start
  const onStart = () => inputRef.current.blur();

  return (
    <div>
      <Dialog
        open={true}
        PaperComponent={DraggablePaper}
        // pass in the callback
        PaperProps={{ onStart }}
      >
        <DialogTitle style={{ cursor: "move" }} id={titleId}>
          Title
        </DialogTitle>
        <DialogContent>
          <DialogContentText>Content text</DialogContentText>
          {/* pass in the ref */}
          <ComboBox {...{ inputRef }} />
        </DialogContent>
        <DialogActions>
          <Button>Submit</Button>
          <Button>Cancel</Button>
        </DialogActions>
      </Dialog>
    </div>
  );
}
function ComboBox({ inputRef }) {
  return (
    <Autocomplete
      id="combo-box-demo"
      options={top3Films}
      getOptionLabel={(option) => option.title}
      style={{ width: 300 }}
      renderInput={(params) => (
        <TextField
          {...params}
          label="Combo box"
          variant="outlined"
          // attach the ref
          inputRef={inputRef}
        />
      )}
    />
  );
}
function DraggablePaper({ onStart, ...props }) {
  const nodeRef = React.useRef(null);

  return (
    <Draggable
      handle={`#${titleId}`}
      cancel={'[class*="MuiDialogContent-root"]'}
      nodeRef={nodeRef}
      // attach the callback
      onStart={onStart}
    >
      <Paper ref={nodeRef} {...props} />
    </Draggable>
  );
}
thisisrandy
  • 2,660
  • 2
  • 12
  • 25