akiran / react-slick

React carousel component
http://react-slick.neostack.com/
MIT License
11.76k stars 2.11k forks source link

Keyboard navigation not working #1121

Closed jonaswindey closed 6 years ago

jonaswindey commented 6 years ago

In the default sandbox, arrow key navigation isn't working.

Even adding accessibility: true in the setting doesn't help.

https://codesandbox.io/s/nn15w3w860

Did anything change about this in the last release?

laveesingh commented 6 years ago

Arrow key navigation requires the focus to be on the slider or any of the children. Had to prevent default of swipe event to avoid dragging images instead of slider track, which was troubling for a long time. Currently, clicking on an image won't focus on the slider, you have to click anything except on the image. Then the slider is in focus and key navigation would work. This doesn't sound very good, so I have added an additional event listener: 5bd8d601891c5f5e541b971ec44b5278b4329235 This change will be released with the next version.

jonaswindey commented 6 years ago

Thanks. Even clicking outside the image currently doesn't work for the default sandbox.

laveesingh commented 6 years ago

@jonaswindey I'm not sure if that's the case. Following is the behaviour of the very same example you posted above after clicking right next to the image. accessibility test

jonaswindey commented 6 years ago

You're right. You have to click exactly between the image and the arrow. I suppose that's not the expected behaviour.

laveesingh commented 6 years ago

Not at all intended, but the fixing changes are on the way.

randallreedjr commented 6 years ago

It looks like this was released in 0.22.0, but the arrow keys still aren't working for me (0.23.1). Is there another issue affecting keyboard navigation?

dln88 commented 3 years ago

3 years already and this issue still there

ehsankhan-blitz commented 2 years ago

4 years

williamcardozo commented 1 year ago

5 years and still not working

Enkratia commented 1 year ago

Keyboard navigation for React-slick

To check demo how it works (any slider except first one): https://website-react-portfolio.netlify.app/

If this is acceptable for you:

  1. Insert your slider in wrapper.
  2. Copy the functions to your component.
  3. You`ve done.
See wrapper ```js
// Your slick-slider component
```
See functions ```js const createSliderExit = (e: React.FocusEvent) => { const list = e.currentTarget.querySelector(".slick-list"); const slickExit = document.createElement("span"); slickExit.className = "slick-exit"; slickExit.setAttribute("tabindex", "0"); list?.appendChild(slickExit); }; const startSliderKeyMode = (e: React.FocusEvent | React.KeyboardEvent) => { const firstSlide = e.currentTarget.querySelectorAll(".slick-slide:not(.slick-cloned)")[0]; sliderRef.current?.slickGoTo(0); (firstSlide as HTMLElement)?.focus(); }; const getSliderInfo = (e: React.FocusEvent | React.KeyboardEvent) => { const slide = (e.target as HTMLElement)?.closest(".slick-slide"); const nextSlide = slide?.nextElementSibling; const prevSlide = slide?.previousElementSibling; const isNextSlideClone = nextSlide?.classList.contains("slick-cloned"); const isNextSlideActive = nextSlide?.classList.contains("slick-active"); const isPrevSlideClone = prevSlide?.classList.contains("slick-cloned"); const isPrevSlideActive = prevSlide?.classList.contains("slick-active"); const interactive = slide?.querySelectorAll("a, button, [tabindex='0']") || []; const realInteractive = [...interactive].filter( (elem) => window.getComputedStyle(elem).visibility !== "hidden", ); const firstInteractive = realInteractive[0]; const lastInteractive = realInteractive[realInteractive.length - 1]; return { nextSlide, isNextSlideClone, isNextSlideActive, isPrevSlideClone, isPrevSlideActive, firstInteractive, lastInteractive, }; }; const onSliderBlur = (e: React.FocusEvent) => { if (e.target.hasAttribute("data-key-next")) { e.target.removeAttribute("data-key-next"); return; } if (e.target.hasAttribute("data-key-prev")) { e.target.removeAttribute("data-key-prev"); } }; const onSliderPointerDown = (e: React.MouseEvent) => { e.currentTarget.removeAttribute("data-key-mode"); }; const onSliderKeyDown = (e: React.KeyboardEvent) => { if (e.key !== "Tab") return; e.currentTarget.setAttribute("data-key-mode", ""); const { isNextSlideClone, isNextSlideActive, isPrevSlideClone, isPrevSlideActive, firstInteractive, lastInteractive, } = getSliderInfo(e); const slickExit = e.currentTarget.querySelector(".slick-exit") as HTMLElement; // When client decided not to go next, after pressing tab and getting new slide if ((e.target as HTMLElement).hasAttribute("data-key-next") && e.shiftKey) { e.preventDefault(); (e.target as HTMLElement).removeAttribute("data-key-next"); sliderRef.current?.slickPrev(); return; } // When client decided not to go backward, after pressing shift+tab and getting new slide if ((e.target as HTMLElement).hasAttribute("data-key-prev") && !e.shiftKey) { e.preventDefault(); (e.target as HTMLElement).removeAttribute("data-key-prev"); sliderRef.current?.slickNext(); return; } // First tab on slider if (e.target === e.currentTarget && !e.shiftKey) { startSliderKeyMode(e); return; } // Client got back to slider by shift+tab after leaving it if (slickExit && e.target === slickExit && e.shiftKey) { e.preventDefault(); (e.currentTarget as HTMLElement).focus(); return; } // Check if the next slide is clone and check whether current tab going to go to this clone (or tab will remain inside this slide for now) if (isNextSlideClone && e.target === lastInteractive && !e.shiftKey) { e.preventDefault(); slickExit?.focus(); return; } // Check if the prev slide is clone and check whether current tab going to go to this clone (or tab will remain inside this slide for now) if (isPrevSlideClone && e.target === firstInteractive && e.shiftKey) { e.preventDefault(); (e.currentTarget as HTMLElement)?.focus(); return; } // Show one more slide in slider const islastInteractiveElement = e.target === lastInteractive; if (!isNextSlideActive && !isNextSlideClone && islastInteractiveElement && !e.shiftKey) { e.preventDefault(); // If slider do not have clones (totalSlides === slidesToShow) if (isNextSlideActive === undefined && isNextSlideClone === undefined) { slickExit?.focus(); return; } (e.target as HTMLElement).setAttribute("data-key-next", ""); sliderRef.current?.slickNext(); return; } // Show one more slide in slider // (But for shift+tab) const isfirstInteractiveElement = e.target === firstInteractive; if (!isPrevSlideActive && !isPrevSlideClone && isfirstInteractiveElement && e.shiftKey) { e.preventDefault(); // If slider do not have clones if (isPrevSlideActive === undefined && isPrevSlideClone === undefined) { (e.currentTarget as HTMLElement)?.focus(); return; } (e.target as HTMLElement).setAttribute("data-key-prev", ""); sliderRef.current?.slickPrev(); } }; const onSliderFocus = (e: React.FocusEvent) => { let slickExit = e.currentTarget.querySelector(".slick-exit"); // Create slick-exit (element-helper to go outside when only cloned slides are left in slider) if (!slickExit) { createSliderExit(e); } }; ```
Note There should be atleast one interactive element **inside** of your slide (`React-slick` gives attribute `tab-index="-1"` to every slide by default, that means no interactivity for the slide himself. Interactive elements are: `button`, `a`, `[tab-index="0"]`, `input`, `select`, etc.