drop-downs

drop-down.js

Best way so far

// drop-downs.js

export function initDropDowns() {   
    hideAllCodeCmds()
    if(!document.listenersAdded){

        document.addEventListener("click", handleToggle);
        document.addEventListener("keydown", handleToggle);
        document.listenersAdded = true
    }
    // const dropChilds = document.querySelectorAll('.code-cmd') ? document.querySelectorAll('.code-cmd') : document.querySelectorAll('.topic-snips')
    
    function handleToggle(e) {
        let target;        
        if (e.type === "keydown") {
            if ((e.key === "Enter" || e.key === " ") && (document.activeElement.classList.contains("drop-down") || document.activeElement.classList.contains("drop-code-cmd"))) {
                e.preventDefault();
                target = document.activeElement;
            } else {
                return; // ignore other keys
            }
        } else if (e.type === "click") {
            // Ignore clicks triggered by keyboard
            if (e.detail === 0) return;
            target = e.target.closest(".drop-down") || e.target.closest(".drop-code-cmd");
            if (!target) return;
        }

        // Unified toggle logic
        const topic = target.closest(".topic");
        const snip = target.closest(".snip");
        // console.log("Toggled dropdown:", topic);
        // if (!topic || !snip) return;
        console.log(snip)
        if (snip){
            toggleCodeSnips(snip)
            return
        } 
        if(topic){
            toggleTopicSnips(topic)
            return 
        } 
    }
}

function toggleCodeSnips(snip) {
    const codeCmd = snip.querySelector('.code-cmd')
    codeCmd.classList.toggle('hide')

}
function hideAllCodeCmds() {
    const codeCmds = document.querySelectorAll('.code-cmd')
    codeCmds.forEach(el => {
        if(!el.classList.contains('show')){
            el.classList.add('hide')
        }
    })
    

}
function toggleTopicSnips(topic) {
    const topicSnips = topic.querySelector('.topic-snips')
    topicSnips.classList.toggle("hide"); // example toggle
}

                                

Keyboard Navigation

keyboard-nav-letter-nums.js

BEST VERSION YET
Keyboard Navigation letter nums Script
This script allows quick keyboard-based navigation through visible focusable elements on the page (links and elements with an ID) using single letters or numbers.
Features:
  • - Focus moves to elements whose text starts with the pressed letter or number.
  • - Pressing the same key cycles forward or backward through matching elements (Shift+key for backward).
  • - On a fresh key press, the closest matching element to the currently focused element is chosen.
  • - Invisible elements or those with no size are automatically ignored.
  • - The currently pressed key is tracked to manage cycling behavior.
Usage:
Include this script on a page to enhance keyboard accessibility and quick navigation. */
// keyboard-nav.js
addEventListener('keydown', e => {
    // const activeEl = document.activeElement
    const key = e.key.toLowerCase();

    // Only single alphanumeric characters (letters + numbers)
    if (key.length !== 1 || !/^[a-z0-9]$/.test(key)) return;

    // Get all visible focusable elements
    const focusableEls = [...document.querySelectorAll('a, [id]')].filter(el => {
        const rect = el.getBoundingClientRect();
        return el.offsetParent !== null && rect.width > 0 && rect.height > 0;
    });

    if (!focusableEls.length) return;

    // Filter by first number OR first letter in the element text
    const matchingEls = focusableEls.filter(el => {
        const text = el.innerText.trim().toLowerCase();
        const numMatch = text.match(/[0-9]/);
        const letterMatch = text.match(/[a-z]/);

        return (numMatch && numMatch[0] === key) ||
            (letterMatch && letterMatch[0] === key);
    });

    if (matchingEls.length === 0) return;

    const activeEl = document.activeElement;
    let target;

    if (window.lastLetterPressed === key) {
        // Cycle within the same letter group
        const iMatchingEls = matchingEls.indexOf(activeEl);

        if (e.shiftKey) {
            // backwards in the group
            target = matchingEls[(iMatchingEls - 1 + matchingEls.length) % matchingEls.length];
        } else {
            // forwards in the group
            target = matchingEls[(iMatchingEls + 1) % matchingEls.length];
        }
    } else {
        // Fresh letter press → pick closest match (prefer below if equal)
        const currentIndex = focusableEls.indexOf(activeEl);

        const nextBelow = matchingEls.find(el => focusableEls.indexOf(el) > currentIndex);
        const candidatesAbove = matchingEls.filter(el => focusableEls.indexOf(el) < currentIndex);
        const nextAbove = candidatesAbove.length ? candidatesAbove[candidatesAbove.length - 1] : null;

        if (nextBelow && nextAbove) {
            const distBelow = focusableEls.indexOf(nextBelow) - currentIndex;
            const distAbove = currentIndex - focusableEls.indexOf(nextAbove);
            target = (distBelow <= distAbove) ? nextBelow : nextAbove;
        } else {
            target = nextBelow || nextAbove;
        }
    }

    target?.focus();
    window.lastLetterPressed = key;
});                             

Letter Focus Code

**create pop up with extra info on filter() and findIndex() **make a dropCode like dropTopic so each code example can collapse... great job keep going, you're doing great with implementing the new letterFocus incrementation !!😎

Chat Question: does filter() work the same as findIndex()?
ChatGPT said: Awesome question filter is similar to findIndex, but with one big difference in what it returns.
✅ Both:

  • * Take a callback function that checks a condition.
  • * Go through the array element by element.
  • * Use the callback to test each item.

🔍 Difference: Method Returns findIndex The index of the first matching item filter A new array of all matching items


    const as = document.querySelectorAll('a')
    let asTxtArr = [...as]
    let lastPressedLetter = "";
    let iLetter = 0;
    function updateAsTxtArr(){
        asTxtArr = Array.from(document.querySelectorAll('a'))
    }
    addEventListener('keydown', e => {
        let letter = e.key.toLowerCase()
        let letteredAs = asTxtArr.filter((el) => el.innerText.toLowerCase().startsWith(letter))
        let activeEl = document.activeElement // current focused element
        let activeIndex = [...asTxtArr].indexOf(activeEl)
        if(letter != lastPressedLetter){
            let closestIndex = letteredAs.findIndex((el) => asTxtArr.indexOf(el) > activeIndex)
            iLetter = closestIndex !== -1 ? closestIndex : 0
        } else {
            iLetter = (letteredAs.indexOf(activeEl) + 1) % letteredAs.length
        }
        if(e.shiftKey){
            iLetter = (letteredAs.indexOf(activeEl) - 1 + letteredAs.length)  % letteredAs.length
        }
        if(letteredAs[iLetter]){
            letteredAs[iLetter].focus()
        }
        lastPressedLetter = letter
    })

Incrementing / Decrements Letter Focus

    // This page has really good letter focus cycle
    if(letter == 'c' && !e.metaKey){
        if(!e.shiftKey){
        copyCodes[iCopyCodes].focus()
        iCopyCodes = (iCopyCodes + 1) % copyCodes.length
        } else{
            iCopyCodes = (iCopyCodes - 1 + copyCodes.length) % copyCodes.length
        }
        copyCodes[iCopyCodes].focus()
        console.log(iLetter)
    }

CamelCase

    function toCamelCase(str) {
        return str
        .toLowerCase()
        .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
    }

Drop

2

    // This page has really good letter focus cycle
    if(letter == 'c' && !e.metaKey){
        if(!e.shiftKey){
        copyCodes[iCopyCodes].focus()
        iCopyCodes = (iCopyCodes + 1) % copyCodes.length
        } else{
            iCopyCodes = (iCopyCodes - 1 + copyCodes.length) % copyCodes.length
        }
        copyCodes[iCopyCodes].focus()
        console.log(iLetter)
    }

CamelCase function

    function toCamelCase(str) {
        return str
        .toLowerCase()
        .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
    }

Drop downs

Incrementing / Decrements Letter Focus

    function hideTopics(){

    }

CamelCase function

    function toCamelCase(str) {
        return str
        .toLowerCase()
        .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
    }