UNPKG

@scania/tegel

Version:
191 lines (190 loc) 8.78 kB
// ============================================================================ // Dropdown Keyboard Navigation // Handles arrow keys, Enter, Space, Tab, and Escape for all dropdown variants // ============================================================================ let initialized = false; export function initDropdownKeyboard() { if (initialized) return; initialized = true; // ============================================================================ // Handler for when dropdown list is open // ============================================================================ const handleOpenListKeydown = (e) => { const openTrigger = document.querySelector('.tl-dropdown__button[aria-expanded="true"], .tl-dropdown__input[aria-expanded="true"]'); if (!openTrigger) return; const root = openTrigger.closest('.tl-dropdown'); const openList = root === null || root === void 0 ? void 0 : root.querySelector('.tl-dropdown__list'); if (!openList) return; const isDropUp = root === null || root === void 0 ? void 0 : root.classList.contains('tl-dropdown--dropup'); const isMultiSelect = openList.getAttribute('aria-multiselectable') === 'true'; const isFilterDropdown = !!(root === null || root === void 0 ? void 0 : root.querySelector('.tl-dropdown__input')); const options = Array.from(openList.querySelectorAll('.tl-dropdown__option')).filter((option) => { const el = option; return (!el.classList.contains('tl-dropdown__option--disabled') && !el.classList.contains('tl-dropdown__option--no-result') && el.style.display !== 'none'); }); if (!options.length) return; const { activeElement } = document; const currentIndex = options.findIndex((option) => option === activeElement); const focusOption = (index) => { if (index < 0 || index >= options.length) return; options[index].focus(); }; if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (isFilterDropdown && activeElement === openTrigger) { const isArrowDown = e.key === 'ArrowDown'; const firstIndex = isArrowDown ? isDropUp ? options.length - 1 : 0 : isDropUp ? 0 : options.length - 1; focusOption(firstIndex); return; } const direction = e.key === 'ArrowDown' ? 1 : -1; let nextIndex = currentIndex; if (currentIndex === -1) { const isArrowDown = e.key === 'ArrowDown'; nextIndex = isArrowDown ? isDropUp ? options.length - 1 : 0 : isDropUp ? 0 : options.length - 1; } else { nextIndex = currentIndex + (isDropUp ? -direction : direction); } if (nextIndex >= options.length) nextIndex = 0; if (nextIndex < 0) nextIndex = options.length - 1; focusOption(nextIndex); return; } if (e.key === 'Enter' || e.key === ' ') { const focusedOption = options[currentIndex]; if (!focusedOption) return; if (isMultiSelect) return; e.preventDefault(); focusedOption.click(); if (openTrigger) openTrigger.setAttribute('aria-expanded', 'false'); if (!isFilterDropdown) { openTrigger === null || openTrigger === void 0 ? void 0 : openTrigger.focus(); } return; } if (e.key === 'Tab') { if (isFilterDropdown) { const inputWrapper = openTrigger.parentElement; const clearButton = inputWrapper === null || inputWrapper === void 0 ? void 0 : inputWrapper.querySelector('.tl-dropdown__input-clear'); const clearButtonTabindex = clearButton === null || clearButton === void 0 ? void 0 : clearButton.getAttribute('tabindex'); const canFocusClearButton = clearButton && clearButtonTabindex === '0'; if (currentIndex >= 0) { if (!e.shiftKey && canFocusClearButton) { e.preventDefault(); clearButton === null || clearButton === void 0 ? void 0 : clearButton.focus(); return; } return; } return; } e.preventDefault(); const movingForward = isDropUp ? e.shiftKey : !e.shiftKey; const nextIndex = movingForward ? currentIndex + 1 : currentIndex - 1; if (nextIndex >= options.length || nextIndex < 0) { if (openTrigger) openTrigger.setAttribute('aria-expanded', 'false'); openTrigger === null || openTrigger === void 0 ? void 0 : openTrigger.focus(); return; } focusOption(nextIndex); return; } if (e.key === 'Escape') { e.preventDefault(); if (openTrigger) openTrigger.setAttribute('aria-expanded', 'false'); openTrigger === null || openTrigger === void 0 ? void 0 : openTrigger.focus(); } }; const handleTriggerKeydown = (e) => { var _a; const trigger = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest('.tl-dropdown__button, .tl-dropdown__input'); if (!trigger) return; const root = trigger.closest('.tl-dropdown'); const list = root === null || root === void 0 ? void 0 : root.querySelector('.tl-dropdown__list'); if (!list) return; const isDropUp = root === null || root === void 0 ? void 0 : root.classList.contains('tl-dropdown--dropup'); const isOpen = trigger.getAttribute('aria-expanded') === 'true'; const options = Array.from(list.querySelectorAll('.tl-dropdown__option')).filter((option) => { const el = option; return !el.classList.contains('tl-dropdown__option--disabled') && el.style.display !== 'none'; }); if (!options.length) return; if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && !isOpen) { e.preventDefault(); trigger.setAttribute('aria-expanded', 'true'); const firstIndex = isDropUp ? options.length - 1 : 0; options[firstIndex].focus(); } }; const handleClearButtonKeydown = (e) => { const target = e.target; if (!target.classList.contains('tl-dropdown__input-clear')) return; if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); target.click(); } if (e.key === 'Tab' && e.shiftKey) { const root = target.closest('.tl-dropdown'); const list = root === null || root === void 0 ? void 0 : root.querySelector('.tl-dropdown__list'); if (list) { const options = Array.from(list.querySelectorAll('.tl-dropdown__option')).filter((option) => { const el = option; return (!el.classList.contains('tl-dropdown__option--disabled') && !el.classList.contains('tl-dropdown__option--no-result') && el.style.display !== 'none'); }); if (options.length > 0) { e.preventDefault(); options[options.length - 1].focus(); } } } }; const handleClearButtonFocus = (e) => { var _a; const target = e.target; if (!target.classList.contains('tl-dropdown__input-clear')) return; const input = (_a = target .closest('.tl-dropdown__input-wrapper')) === null || _a === void 0 ? void 0 : _a.querySelector('.tl-dropdown__input'); if (input) { input.setAttribute('aria-expanded', 'true'); } }; document.addEventListener('keydown', handleOpenListKeydown); document.addEventListener('keydown', handleTriggerKeydown); document.addEventListener('keydown', handleClearButtonKeydown); document.addEventListener('focus', handleClearButtonFocus, true); }