繁体   English   中英

如何实现 function 以使用箭头键浏览我的反应表?

[英]How do I implement a function to navigate through my react table using arrow keys?

我有一个 15 x 15 的反应表,每个单元格都包含输入字段。 我想实现一个 function ,这样每次我按下箭头键时,它都会将焦点移到那个方向。 到目前为止,这是我的董事会。 任何帮助表示赞赏!

 let rows = [];
 for (var i = 0; i < 15; i++){
   let rowID = `row${i}`
   let cell = []
   for (var idx = 0; idx < 15; idx++){
     let cellID = `cell${i}-${idx}`
     let row = parseInt(`${i}`)
     let col = parseInt(`${idx}`)
     cell.push(
      <td key={cellID} id={cellID}>
         <div className={"tile"}>
              <input>
              </input>
           </div>
        </td>)
      }
   rows.push(<tr key={i} id={rowID}>{cell}</tr>)
 }
 return (
  <div className="board">
   <table>
    {rows}
   </table>
  </div>
  );
}```

要添加键盘控件,您需要处理以下内容:

  • 知道哪个单元格处于活动状态(useState)
  • 知道用户是在编辑、导航还是两者都没有(useState)
  • 存储输入值 (useState)
  • 参考板(useRef)
  • 对输入元素的引用 (useRef)
  • 处理 mousedown 和 keydown 事件(事件监听器,useEffect)
  • 处理导航、编辑、索引等更改的副作用(useEffect、useCallback)
  • 用户正在导航与编辑的可视化 (CSS)

这是我添加用户控件的方法,如果您愿意的话。 我敢打赌,您会更喜欢实施自己的解决方案。 我确信可以清理这段代码,但这是第一次通过。

你可以在这里尝试演示

import React, { useCallback, useEffect, useRef, useState } from "react";

const SimpleTable = () => {
  const [numRows, numCols] = [3, 3]; // No magic numbers
  const [activeIndex, setActiveIndex] = useState(-1); // Track which cell to highlight
  const [isNavigating, setIsNavigating] = useState(false); // Track navigation
  const [isEditing, setIsEditing] = useState(false); // Track editing
  const [values, setValues] = useState([]); // Track input values
  const boardRef = useRef(); // For setting/ unsetting navigation
  const inputRefs = useRef([]); // For setting / unsetting input focus

  // Handle input changes to store the new value
  const handleChange = (e) => {
    const { value } = e;
    const newValues = Array.from(values);
    newValues[activeIndex] = value;
    setValues(newValues);
  };

  // Handle mouse down inside or outside the board
  const handleMouseDown = useCallback(
    (e) => {
      if (boardRef.current && boardRef.current.contains(e.target)) {
        if (e.target.className === "cell-input") {
            setIsNavigating(true);
            setIsEditing(true);
        }
      } else {
            setIsNavigating(false);
      }
    },
    [boardRef, setIsNavigating]
  );

  // Handle key presses: 
  // arrows to navigate, escape to back out, enter to start / end editing
  const handleKeyDown = useCallback(
    (e) => {
      if (isNavigating) {
        const { key } = e;
        switch (key) {
            case "ArrowUp":
                // Move up a row, subtract num cols from index
                if (!isEditing && activeIndex >= numRows)
                    setActiveIndex(activeIndex - numCols);
                break;
            case "ArrowDown":
                // Move down a row, add num cols to index
                if (!isEditing && activeIndex < numRows * numCols - numCols)
                    setActiveIndex(activeIndex + numCols);
                break;
            case "ArrowRight":
                // Move one col right, add one
                if (!isEditing && activeIndex < numRows * numCols - 1)
                    setActiveIndex(activeIndex + 1);
                break;
            case "ArrowLeft":
                // Move one col left, subtract one
                if (!isEditing && activeIndex > 0) setActiveIndex(activeIndex - 1);
                break;
            case "Enter":
                if (isEditing) setIsEditing(false);
                else if (isNavigating) setIsEditing(true);
                else if (!isEditing) setIsNavigating(true);
                break;
            case "Escape":
                // Stop navigating
                if (isEditing) setIsEditing(false);
                else if (isNavigating) setIsNavigating(false);
                break;
            default:
                break;
        }
      }
    },
    [activeIndex, isNavigating, isEditing, numRows, numCols]
  );

  // Add listeners on mount, remove on unmount
  useEffect(() => {
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("keydown", handleKeyDown);

    return () => {
        window.removeEventListener("mousedown", handleMouseDown);
        window.removeEventListener("keydown", handleKeyDown);
    };
  }, [handleMouseDown, handleKeyDown]);

  // When the index changes, determine if we should focus or blur the current input
  const onIndexChange = useCallback(() => {
    if (activeIndex >= 0 && activeIndex < numRows * numCols) {
        const inputRef = inputRefs.current[activeIndex];
        if (inputRef) {
            if (isEditing) {
                inputRef.focus();
            } else {
                inputRef.blur();
            }
        }
    }
  }, [activeIndex, isEditing, inputRefs, numRows, numCols]);
  useEffect(onIndexChange, [activeIndex, onIndexChange]);

  // When isEditing changes focus or blur the current input
  const onIsEditingChange = useCallback(() => {
    const inputRef = inputRefs.current[activeIndex];
    if (!inputRef) return;

    if (isNavigating && isEditing) {
        inputRef.focus();
    } else if (!isEditing) {
        inputRef.blur();
    }
  }, [inputRefs, isEditing, activeIndex, isNavigating]);
  useEffect(onIsEditingChange, [isEditing, onIsEditingChange]);

  // When isNavigating changes, set the index to 0 or -1 (if not navigating)      
  const onIsNavigatingChange = useCallback(() => {
    if (!isNavigating) {
        setActiveIndex(-1);
    } else if (activeIndex < 0) {
        setActiveIndex(0);
    }
  }, [isNavigating, setActiveIndex, activeIndex]);
  useEffect(onIsNavigatingChange, [isNavigating, onIsNavigatingChange]);
  
  // Your original code with minor changes
  let rows = [];
  for (var i = 0; i < numRows; i++) {
    let rowID = `row${i}`;
    let cell = [];
    for (var idx = 0; idx < numCols; idx++) {
        let cellID = `cell${i}-${idx}`;
        const index = i * numCols + idx;
        cell.push(
            <td key={cellID} id={cellID}>
            <div className={`tile ${activeIndex === index ? "active" : ""}`}>
                <input
                value={values[activeIndex]}
                onChange={handleChange}
                className="cell-input"
                onFocus={() => setActiveIndex(index)}
                ref={(el) => (inputRefs.current[index] = el)}
                />
            </div>
            </td>
        );
    }
    rows.push(
        <tr key={i} id={rowID}>
            {cell}
        </tr>
    );
  }

  return (
    <div className="board" ref={boardRef}>
        <table>
            <tbody>{rows}</tbody>
        </table>
    </div>
  );
};

export default SimpleTable;

这里还有一个小 CSS,我用它来显示哪个单元格处于活动状态:

.tile.active {
  border: 1px solid rgb(0, 225, 255);
}

.tile {
  border: 1px solid black;
}

.cell-input {
  border: none;
  outline: none !important;
}

如果您对具体细节有疑问,请告诉我!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM