最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to update a ref with a signal in Solid.js to control table scroll? - Stack Overflow

programmeradmin5浏览0评论

I'm working with Solid.js and have a table with rows and cells. I'm using signals to manage the selected row and cell indices, but I need to update a ref (reference to the selected cell) based on those signals in order to manipulate the table's scroll when the content overflows.

The issue is that I don't want to use querySelector, as I don't consider it a good practice, and I also want the reference to update according to the state of the signals.

Here is a simplified version of my implementation:

import { batch, Component, createEffect, createSelector, createSignal, For, JSX } from "solid-js";

export const DataGrid: Component = () => {
  const rows = Array.from({ length: 10 });
  const cells = Array.from({ length: 10 });

  const [selectedRowIndex, setSelectedRowIndex] = createSignal(0);
  const [selectedCellIndex, setSelectedCellIndex] = createSignal(0);

  const isSelectedRow = createSelector(selectedRowIndex);
  const isSelectedCell = createSelector(selectedCellIndex);

  const keysToPrevent = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];

  const onKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (event) => {
    const { key, ctrlKey } = event;

    if (keysToPrevent.includes(key)) {
      event.preventDefault();
    }

    const rowLength = rows.length;
    const cellLength = cells.length;

    switch (key) {
      case "ArrowUp":
        if (ctrlKey) {
          setSelectedRowIndex(0);
        } else if (selectedRowIndex() > 0) {
          setSelectedRowIndex((v) => v - 1);
        }
        break;

      case "ArrowDown":
        if (ctrlKey) {
          setSelectedRowIndex(rowLength - 1);
        } else if (selectedRowIndex() < rowLength - 1) {
          setSelectedRowIndex((v) => v + 1);
        }
        break;

      case "ArrowLeft":
        if (ctrlKey) {
          setSelectedCellIndex(0);
        } else if (selectedCellIndex() > 0) {
          setSelectedCellIndex((v) => v - 1);
        }
        break;

      case "ArrowRight":
        if (ctrlKey) {
          setSelectedCellIndex(cellLength - 1);
        } else if (selectedCellIndex() < cellLength - 1) {
          setSelectedCellIndex((v) => v + 1);
        }
        break;
    }
  };

  const onMouseDown: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {
    const rowIndex = parseInt(event.currentTarget.dataset.rowIndex!);
    const colIndex = parseInt(event.currentTarget.dataset.colIndex!);

    batch(() => {
      setSelectedRowIndex(rowIndex);
      setSelectedCellIndex(colIndex);
    });
  };

  let tableRef!: HTMLDivElement;
  let selectedCellRef!: HTMLDivElement;

  createEffect(() => {
    selectedRowIndex();
    selectedCellIndex();

    /* I need to update scrollTop and scrollLeft of the table
       when the selected cell changes. */
  });

  return (
    <div class="table" tabIndex={0} onKeyDown={onKeyDown} ref={tableRef}>
      <For each={rows}>
        {(_, rowIndex) => (
          <div class="row" classList={{ selected: isSelectedRow(rowIndex()) }}>
            <For each={cells}>
              {(_, cellIndex) => (
                <div
                  class="cell"
                  classList={{ selected: isSelectedRow(rowIndex()) && isSelectedCell(cellIndex()) }}
                  data-row-index={rowIndex()}
                  data-col-index={cellIndex()}
                  onMouseDown={onMouseDown}
                >
                  row {rowIndex()} - cell {cellIndex()}
                </div>
              )}
            </For>
          </div>
        )}
      </For>
    </div>
  );
};
*,
*::after,
*::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --cell-height: 27px;
  --cell-border-color: #caddf7;
}

html {
  font-size: 12px;
  font-family: "Tahoma";
}

a,
input,
button,
select,
textarea {
  font-size: inherit;
  font-family: inherit;
}

body {
  padding: 20px;
}

.table {
  width: 400px;
  height: 400px;
  outline: none;
  overflow: auto;
  border: 1px solid #8ea6c3;
  user-select: none;
}

.row {
  display: flex;
  width: fit-content;
}

.cell {
  width: 100px;
  flex-shrink: 0;
  height: var(--cell-height);
  line-height: var(--cell-height);
  padding-inline: 4px;
  border-right: 1px solid var(--cell-border-color);
  border-bottom: 1px solid var(--cell-border-color);

  &.selected {
    box-shadow: inset 0 0 0 1px blue;
  }
}
发布评论

评论列表(0)

  1. 暂无评论