Home > Software engineering >  Click outside to leave the text editing without triggering any other clickable elements
Click outside to leave the text editing without triggering any other clickable elements

Time:07-22

Sometimes in the card we got title which can be clicked to edit, after edited we to leave the editing mode by blurring. However, there are a lot of clickable UI outside this text input, and I don't want them to be triggered. Simply apply onBlur event to leave editing mode cannot avoid triggering other components.

enter image description here

Here's my code which demonstrated this issue: (CodeSandBox)

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const itemList = [
    { done: false, title: "1st task" },
    { done: false, title: "2nd task" },
    { done: true, title: "3rd task" }
  ];

  const [todoItemList, setTodoItemList] = useState(itemList);

  const rename = (newName, index) => {
    setTodoItemList(
      itemList.map((item, i) => {
        if (i === index) {
          item.title = newName;
        }
        return item;
      })
    );
  };

  return (
    <div className="App">
      {todoItemList.map((item, index) => (
        <TodoItem
          key={index}
          done={item.done}
          title={item.title}
          rename={(newName) => rename(newName, index)}
        />
      ))}
    </div>
  );
}

const TodoItem = ({ done, title, rename }) => {
  const [isEdit, setIsEdit] = useState(false);

  const submit = (e) => {
    rename(e.target.value);
    setIsEdit(false);
  };

  const handleOnKeyUp = (e) => {
    if (e.key === "Enter") {
      submit(e);
    }
  };

  return (
    <div
      style={{
        border: "1px black solid",
        margin: "10px auto",
        padding: "5px 0"
      }}
    >
      <input type="checkbox" defaultChecked={done} />
      {isEdit ? (
        <input
          autoFocus
          type="text"
          defaultValue={title}
          onBlur={submit}
          onKeyUp={handleOnKeyUp}
        />
      ) : (
        <span onClick={() => setIsEdit(true)}>{title}</span>
      )}
    </div>
  );
};

Take the above one as an example, if you click on the "3rd task" to edit the title, and you try to leave edit mode, whether you can use enter key or click outside the text input. While you do the latter, trying to leave the edit mode of title 3, either clicking on other titles, or clicking on checkboxes, they are all triggered at the same time. If there's an overlay on the top to block all the possible clicks, then the original input text will be unclickable as well.

What I want to achieve is that when I was in the edit mode, any clicking outside the text input area does one thing: leave edit mode. At the same time, the original text input should stay clickable (editable). Can this be achievable? Does anyone have idea of how to do it?

CodePudding user response:

You can create a transparent div behind the input form that covers the viewport. That way you click on the div and it doesn't trigger any clicks.

Edit: As @sdym the OP noted, the absolutely positioned div, now covers the input. So we also set the input's z-index property to raise it above the div.

CodeSandbox

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const itemList = [
    { done: false, title: "1st task" },
    { done: false, title: "2nd task" },
    { done: true, title: "3rd task" }
  ];

  const [todoItemList, setTodoItemList] = useState(itemList);

  const rename = (newName, index) => {
    setTodoItemList(
      itemList.map((item, i) => {
        if (i === index) {
          item.title = newName;
        }
        return item;
      })
    );
  };

  return (
    <div className="App">
      {todoItemList.map((item, index) => (
        <TodoItem
          key={index}
          done={item.done}
          title={item.title}
          rename={(newName) => rename(newName, index)}
        />
      ))}
    </div>
  );
}

const TodoItem = ({ done, title, rename }) => {
  const [isEdit, setIsEdit] = useState(false);

  const submit = (e) => {
    rename(e.target.value);
    setIsEdit(false);
  };

  const handleOnKeyUp = (e) => {
    if (e.key === "Enter") {
      submit(e);
    }
  };

  return (
    <div
      style={{
        border: "1px black solid",
        margin: "10px auto",
        padding: "5px 0"
      }}
    >
      <input type="checkbox" defaultChecked={done} />
      {isEdit ? ( // jsx fragment in the line below
        <>
          <div
            style={{
              // this is the div
              position: "absolute",
              left: 0,
              top: 0,
              width: "100%",
              height: "100%"
            }}
          ></div>
          <input
            style={{
              position: "relative",
              zIndex: 1
            }}
            autoFocus
            type="text"
            defaultValue={title}
            onBlur={submit}
            onKeyUp={handleOnKeyUp}
          />
        </> // Close jsx fragment
      ) : (
        <span onClick={() => setIsEdit(true)}>{title}</span>
      )}
    </div>
  );
};
  • Related