要添加键盘控件,需要处理以下内容:
- 了解哪个单元格处于活动状态 (useState)
- 了解用户是在编辑、导航还是两者都没有 (useState)
- 存储输入值 (useState)
- 对板的引用 (useRef)
- 对输入元素的引用 (useRef)
- 处理 mousedown 和 keydown 事件(事件侦听器,useEffect)
- 处理导航、编辑、索引等更改的副作用(useEffect、useCallback)
- 为用户提供他们正在导航与编辑 (CSS) 的可视化效果
这是我添加用户控件的方法,如果您愿意的话。我敢打赌,您会更喜欢实施自己的解决方案。我确信这段代码可以被清理,但这是第一次通过。
你可以试试演示here
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;
}
如果您对具体细节有任何疑问,请告诉我!