0

This is not a duplicate question. I have gone through existing answers and have applied the same solution but it hasn't quite worked.

I have a list of Cards and I have addEventListener set up to track ArrowRight and ArrowLeft. I am not quite sure how I can get it to focus and move through the cards using arrow keys.

I tried the following but I get an error ".card:focus".next is not a function

  useEffect(() => {
    document.addEventListener("keydown", (e) => {
      if (e.key === "ArrowRight") {
        ".card:focus".next().focus();
      }
      if (e.key === "ArrowLeft") {
        ".card:focus".prev().focus();
      }
    });
  });

This is what the DOM looks like:

enter image description here

Parent component:

import React, { useEffect, useState } from "react";
import { getPokemons } from "../graphql/fetch/getPokemons.js";
import styled from "styled-components";
import "../components/pokemon-card";

const Cards = styled.div`
  display: flex;
`;

export default function Pokemon() {
  const [isLoading, setIsLoading] = useState(true);
  const [pokemons, setPokemons] = useState([]);
  useEffect(() => {
    getPokemons().then((response) => {
      console.log(response.data.pokemons);
      setPokemons(response.data.pokemons);
      setIsLoading(false);
    });
  }, []);

  useEffect(() => {
    document.addEventListener("keydown", (e) => {
      if (e.key === "ArrowRight") {
        ".card:focus".next().focus();
      }
      if (e.key === "ArrowLeft") {
        ".card:focus".prev().focus();
      }
    });
  });

  if (isLoading) {
    return <p>is loading</p>;
  }

  return (
    <Cards>
      {pokemons.map((pokemon) => (
        <pokemon-card name={pokemon.name} image={pokemon.image}></pokemon-card>
      ))}
    </Cards>
  );
}

Child component:

import { LitElement, html, css } from "lit-element";

class PokemonCard extends LitElement {
  static styles = css`
    .card {
      background: white;
      border-radius: 1rem;
      padding: 2rem;
      box-shadow: 4px 4px 12px 2px rgba(0, 0, 0, 0.75);
      height: 500px;
      transition: 0.2s;
    }
    .card:hover,
    .card:focus-within {
      transform: translateY(-5rem);
    }
  `;

  static get properties() {
    return {
      name: { type: String },
      image: { type: String },
    };
  }

  render() {
    const { name, image } = this;

    return html`
      <div class="card">
        <p>${name}</p>
        <img src=${image} />
      </div>
    `;
  }
}

customElements.get("pokemon-card") ||
  customElements.define("pokemon-card", PokemonCard);
coding1223322
  • 461
  • 11
  • 26

1 Answers1

0

Every event has a target property. The target property of the Event interface is a reference to the object onto which the event was dispatched.

If the nodes are, like on your screenshot, all on the same level, you can make use of:

// Using ElementChild and ElementSibling skips text-nodes
// if there is no need for skipping them, make use of nextChild, 
// nextSibling and so on

// in case of right arrow:
if (e.target.classList.contains("card")) {
  if (e.target.nextElementSibling) {
    e.target.nextElementSibling.focus()
  } else {
    e.target.parentNode.firstElementChild.focus();
  }
}

// in case of left arrow:
if (e.target.classList.contains("card")) {
  if (e.target.previousElementSibling) {
    e.target.previousElementSibling.focus()
  } else {
    e.target.parentNode.lastElementChild.focus();
  }
}

Just a note:

Keep in mind, that elements, that can have the focus are limited. They must be an anchor (with href attribute set), button, input, textarea, select (not disabled), contenteditable, iframe or require the tabindex-attribute to be set. (Also refer to this answer)

If you just want to scroll to an element, you could make use of element.scrollIntoView() and add an "active" class or attribute to the current viewed card selected with left or right.

Christopher
  • 3,124
  • 2
  • 12
  • 29