0

语境

  • 我们这里只使用键盘,没有鼠标点击。
  • 图片我们有 10 个表格行。
  • 每行都有一个菜单按钮。
  • 选项卡将弹出 4 项下拉菜单。(焦点在菜单按钮上)
  • 按向下箭头,焦点应位于下拉菜单的第一项(在我的代码中不起作用)

代码

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

const MyDropdown = ({ items, rowKey, disabled }) => {
  const listRefs = useRef([]);
  const [open, setOpen] = useState(false);
  const [uniqueKey, setUniqueKey] = useState("");
  const [selectedIndex, setSelectedIndex] = useState(-1);

  // focus
  useEffect(() => {
    if (open && selectedIndex > -1) {
      // this not working?????????
      listRefs.current[selectedIndex].focus();
    }
  }, [open, selectedIndex]);

  const setActionMenuDefaultState = (
    event,
    isPreventDefault,
    open,
    rowKey,
    selectedIndex
  ) => {
    if (isPreventDefault) event.preventDefault();
    setOpen(open);
    setUniqueKey(rowKey);
    setSelectedIndex(selectedIndex);
  };

  return (
    <div id={"bla" + rowKey}>
      <MyMenuButton
        onKeyDown={event => {
          if ([ENTER_KEY, SPACE_KEY].includes(event.key)) {
            //test
            console.log("enter or space");
            setActionMenuDefaultState(event, true, !open, rowKey, -1);
          } else if ([ARROW_DOWN_KEY].includes(event.key)) {
            //test
            console.log("down key");
            event.preventDefault();

            // menu is open
            if (open) {
              // bound
              if (selectedIndex < items.length) {
                //test
                console.log("still set");
                setSelectedIndex(selectedIndex + 1);
              }
              // focus this row, 1st item
            }
          }
        }}
      />
      {!disabled && open && uniqueKey === rowKey ? (
        <MyDivWrapper>
          {items.map((item, index) => {
            return (
              <MyList
                ref={listRef => (listRefs.current[index] = listRef)}
                key={rowKey + index}
                onKeyDown={event => {
                  console.log("Able to focus and enter");
                }}
              >
                <p>Download</p>
              </MyList>
            );
          })}
        </MyDivWrapper>
      ) : null}
    </div>
  );
};

export default MyDropdown;

我的理解

  • 我看这个例子这个
  • 参考数组const listRefs = useRef([]);
  • 我的列表是li
  • ref={listRef => (listRefs.current[index] = listRef)}
4

1 回答 1

0

找到了解决方案。亮点是

  • 创建一个对象并分配喜欢obj[index] = createRef
  • 需要引入 the_row_index (父索引)以及项目自身
  • menuItemRefs.current[parentRowIndex + menuItemActiveIndex].focus();访问每个菜单项menuItemRefs[parentRowIndex + menuItemActiveIndex].current...
  • 并将其用于useEffect
  • 分配ref={element =>(menuItemRefs.current[parentRowIndex + itemIndex] = element)}_li
  • 最重要的,tabIndex="0"必须在li
import React, {useState, useEffect, useRef, createRef, useContext} from 'react';

const AppContext = React.createContext({
  name: 'AppContext'
});

function createMenuItemRefs(items, rowInd) {
  // obj
  let menuItemRefs = {};
  // loop each
  for (let i = 0; i < Object.keys(items).length; i++) {
    // When assign createRef, no current
    menuItemRefs[rowInd + i] = createRef();
  }
  return menuItemRefs;
}

function Menu({buttonName, parentRowIndex}) {
  const [currRowInd, setCurrRowInd] = useState('');
  const [open, setOpen] = useState(false);
  // press down key, will get 1st item which at index 0
  const [menuItemActiveIndex, setMenuItemActiveIndex] = useState(-1);

  const menuItems = {download: 'download', view: 'view', delete: 'delete'};
  const menuItemRefs = useRef(createMenuItemRefs(menuItems, parentRowIndex));

  // focus
  useEffect(() => {
    if (open && menuItemActiveIndex >= 0 && parentRowIndex !== '') {
      console.log('focus!!');
      menuItemRefs.current[parentRowIndex + menuItemActiveIndex].focus();
    }
  }, [menuItemActiveIndex, open, parentRowIndex]);

  const clickOutside = event => {
    if (event.target.nodeName !== 'HTML') {
      setOpen(false);
      return;
    }
    setOpen(false);
  };

  // close
  useEffect(() => {
    if (open) {
      document.addEventListener('click', clickOutside);
    } else {
      document.removeEventListener('click', clickOutside);
    }

    return () => {
      document.removeEventListener('click', clickOutside);
    };
  }, [open]);

  const buttonIconOnClick = (event, parentRowIndex) => {
    // close it
    setOpen(!open);
    // which one to close
    setCurrRowInd(parentRowIndex);
  };

  // on the button level
  const buttonIconKeyDown = (event, parentRowIndex) => {
    if (event.keyCode === 13) {
      // Enter pressed
      console.log('enter is pressed');

      setOpen(!open);
      setCurrRowInd(parentRowIndex);
    } else if (event.keyCode === 9) {
      // we cannot tab away
    } else if (event.keyCode === 40) {
      //test
      console.log('down arrow');

      // 38 is up arrow

      // No scrolling
      event.preventDefault();

      // set to 1st item in 0 index
      setMenuItemActiveIndex(0);
    }
  };

  const itemOnKeyDown = (event, itemIndex) => {
    if (event.keyCode === 13) {
      console.log('enter press fire redux event', itemIndex);
    } else if (event.keyCode === 40) {
      if (itemIndex < Object.keys(menuItems).length - 1) {
        console.log('down');
        setMenuItemActiveIndex(itemIndex + 1);
      }
    } else if (event.keyCode === 38) {
      if (itemIndex > 0) {
        console.log('up');
        setMenuItemActiveIndex(itemIndex - 1);
      }
    }
  };

  const itemOnClick = (event, itemIndex) => {
    // it is mouse click
    if (event.pageX !== 0 && event.pageY !== 0) {
      console.log('item click fire redux event', itemIndex);
    }
  };

  return (
    <div>
      <button
        onClick={event => {
          // it is mouse click
          if (event.pageX !== 0 && event.pageY !== 0) {
            //test
            console.log('parent buttonicon onclick: ');
            buttonIconOnClick(event, parentRowIndex);
          }
        }}
        onKeyDown={event => {
          //test
          console.log('parent buttonicon onkeydown: ');
          buttonIconKeyDown(event, parentRowIndex);
        }}
      >
        {buttonName}
      </button>

      {open && parentRowIndex === currRowInd && (
        <ul style={{padding: '5px', margin: '10px', border: '1px solid #ccc'}}>
          {Object.keys(menuItems).map((item, itemIndex) => {
            if (itemIndex === menuItemActiveIndex)
              return (
                <li
                  tabIndex="0"
                  key={itemIndex}
                  style={{
                    listStyle: 'none',
                    padding: '5px',
                    backgroundColor: 'blue'
                  }}
                  // put a ref
                  ref={element =>
                    (menuItemRefs.current[parentRowIndex + itemIndex] = element)
                  }
                  onClick={event => {
                    itemOnClick(event, itemIndex);
                  }}
                  // we want own index
                  onKeyDown={event => itemOnKeyDown(event, itemIndex)}
                >
                  {item}
                </li>
              );
            else
              return (
                <li
                  tabIndex="0"
                  key={itemIndex}
                  style={{listStyle: 'none', padding: '5px'}}
                  ref={element =>
                    (menuItemRefs.current[parentRowIndex + itemIndex] = element)
                  }
                  onClick={event => {
                    itemOnClick(event, itemIndex);
                  }}
                  // we want own index
                  onKeyDown={event => itemOnKeyDown(event, itemIndex)}
                >
                  {item}
                </li>
              );
          })}
        </ul>
      )}
    </div>
  );
}

function TableElement() {
  const items = [
    {
      file: 'file1',
      button: 'button1'
    },
    {
      file: 'file2',
      button: 'button2'
    },
    {
      file: 'file3',
      button: 'button3'
    }
  ];
  return (
    <table style={{borderCollapse: 'collapse', border: '1px solid black'}}>
      <tbody>
        {items.map((item, index) => {
          return (
            <tr key={index}>
              <td style={{border: '1px solid black'}}>
                <a href="#">{item.file}</a>
              </td>
              <td style={{border: '1px solid black'}}>
                <Menu buttonName={item.button} parentRowIndex={index} />
              </td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

function App() {
  const appContextObj = {};

  return (
    <>
      <AppContext.Provider value={appContextObj}>
        <TableElement />
      </AppContext.Provider>
    </>
  );
}

export default App;
于 2019-12-19T22:51:59.437 回答