javascript - Tabindex fungerar inte i Chrome (React-app)


Jag använder React och react-modal för att skapa ett överlägg över en webbplats. Detta överlägg innehåller olika element och även ett formulär (översikt nedan). Jag vill kunna guida användaren genom formuläret med TAB-tangenter. Jag tilldelade tabindex=0 till de nödvändiga elementen för att kunna tabbas i ordning efter utseende.

Mitt problem är: Det fungerar inte i Chrome (Version 61.0.3163.100) medan det fungerar i Firefox. Jag läste att detta händer om något element uppåt i DOM-trädet är osynligt eller har höjd/bredd på 0. Jag gjorde några stiländringar för att fixa det men utan effekt.

<div class="ReactModalPortal">
<div data-reactroot="" class="ReactModal__Overlay" style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px;">
    <div class="ReactModal__Content" tabindex="-1" aria-label="Questionnaire" style="position: absolute; top: 0px; left: 0px; right: 0px; height: 100%; background: transparent none repeat scroll 0% 0%; overflow: auto;">
        <!-- Some other stuff and nested elements -->
        <div id="...">
            <form>
                <input tabindex="0">
                <button tabindex="0">
            </form> 
        </div>   
    </div>   
</div>    

Som du kan se har ett av föräldraelementen tabindex="-1". När man ändrar det genom inspektionsfunktionen i Chrome eller programmeringsmässigt med JS kvarstår problemet fortfarande (eller är det en skillnad om elementet initialt renderades med detta index?).

Uppdatering

Jag insåg att något annat orsakade problemen. Jag använde CSS-attributet initial: all på root-noden för min modal för att avgränsa min inre CSS från allt utanför. Av någon anledning förhindrade detta att tabindex fungerade. Om du kan hjälpa mig att förstå kommer jag att belöna detta med bounty. Min lösning är helt enkelt att inte använda all: initial (det är inte kompatibelt med IE ändå, men det finns heller inget riktigt bra alternativ som jag känner till).

Svar #1

all: initial återställer alla CSS-egenskaper för noden till initiala egenskaper.

För display-egenskapen skulle standardvärdet vara inline.

Så, att sätta all: initial på root-div:en skulle sätta display-egenskapen till inline. Ett inline-element har ingen höjd eller bredd, så dessa är 0x0.

Detta beror också på att div bara innehåller fasta, absolut positionerade element.

React Modal kontrollerar om element är fokuserbara genom att köra en loop genom alla element inne i modal. För att ett element ska vara fokuserbart måste det dock vara synligt. För varje element måste vi iterera upp till body-elementet för att säkerställa dess synlighet.

Här är funktionen som kontrollerar om elementet är synligt.

function hidden(el) {
  return (
    (el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none"
  );
}

Som du kan se, skulle vår div inte ha någon offsetHeight eller offsetWidth och skulle anses som dold. Därför kan modalen inte fokuseras.

Svar #2

Jag hade samma problem och kunde inte få andra lösningar att fungera snabbt, så jag kom på en kraftfull metod. Gör en referens till containerelementet som håller de fokuserbara elementen som du vill göra tabb-bara.

  const formRef = useRef();

  <ReactModalTabbing containerRef={formRef}>
        <form ref={formRef} onSubmit={handleSubmit}  >
            <input type="text" />
            <input type="text" />
            <input type="text" />
            <input type="text" />
        </form>
  </ReactModalTabbing>

Och detta är komponenten

import React, { useState, useEffect } from 'react';

const ReactModalTabbing = ({ containerRef, children }) => {
    const [configuredTabIndexes, setConfiguredTabIndexes] = useState(false);

    const focusableElements = () => {
        // found this method body here.
        //https://zellwk.com/blog/keyboard-focusable-elements/
        return [...containerRef?.current?.querySelectorAll(
            'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"]):not([type="hidden"]):not([disabled])'
        )];
    }

    const isTabbable = (element) =>{
        if(element.getAttribute('tabindex')){
            return true;
        }
        return false;
    }

    const findElementByTabIndex = (tabIndex) => {
        return containerRef?.current?.querySelector(`[tabindex="${tabIndex}"]`);
    }
    
    const moveFocusToTabIndex = (tabIndex) => {
        findElementByTabIndex(tabIndex)?.focus();
    }

    const handleKeyDownEvent = (event) => {
        if(!isTabbable(event.target)){
            return;
        }

        const tabIndex = parseInt(event.target.getAttribute('tabindex'));

        if(event.shiftKey && event.key === 'Tab'){
            moveFocusToTabIndex(tabIndex - 1);
        }else if(event.key === 'Tab'){ //should probably make sure there is no other modifier key pressed.
            moveFocusToTabIndex(tabIndex + 1);
        }
    }

    useEffect(() => {
        if(!configuredTabIndexes && containerRef.current){
            setConfiguredTabIndexes(true);
            focusableElements().forEach((el, index) => el.setAttribute('tabindex', index + 1));
            containerRef?.current?.addEventListener('keydown', handleKeyDownEvent);
        }
    });

    return children;
}

export default ReactModalTabbing;