UNPKG

@wix/design-system

Version:

@wix/design-system

151 lines (138 loc) 5.21 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _react = require("react"); var useScrollKeyboardNavigation = _ref => { var { containerRef, items, getItemDomId, multiple, onToggle } = _ref; var lastActiveItemIdRef = (0, _react.useRef)(null); var isMouseFocusRef = (0, _react.useRef)(false); var [activeItemId, setActiveItemId] = (0, _react.useState)(null); // Called on mousedown to mark that next focus is from mouse and clear active item var handleMouseDown = (0, _react.useCallback)(() => { isMouseFocusRef.current = true; setActiveItemId(null); }, []); // Get first non-disabled item id var getFirstItemId = (0, _react.useCallback)(() => { var _firstEnabled$id; var firstEnabled = items.find(item => !item.disabled); return (_firstEnabled$id = firstEnabled == null ? void 0 : firstEnabled.id) !== null && _firstEnabled$id !== void 0 ? _firstEnabled$id : null; }, [items]); // Scroll active item into view using DOM id (only scrolls container, not window) var scrollItemIntoView = (0, _react.useCallback)(itemId => { var container = containerRef.current; var domId = getItemDomId == null ? void 0 : getItemDomId(itemId); var element = domId ? document.getElementById(domId) : null; if (!element || !container) return; var containerRect = container.getBoundingClientRect(); var elementRect = element.getBoundingClientRect(); // Check if element is above visible area if (elementRect.top < containerRect.top) { container.scrollTop -= containerRect.top - elementRect.top; } // Check if element is below visible area else if (elementRect.bottom > containerRect.bottom) { container.scrollTop += elementRect.bottom - containerRect.bottom; } }, [containerRef, getItemDomId]); var handleKeyDown = (0, _react.useCallback)(e => { var container = containerRef.current; if (!container) { return; } var onContainer = document.activeElement === container; // Arrow keys: navigate between items (virtual focus) if (onContainer && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { e.preventDefault(); e.stopPropagation(); var enabledItems = items.filter(item => !item.disabled); if (enabledItems.length === 0) return; var currentIndex = enabledItems.findIndex(item => item.id === activeItemId); var direction = e.key === 'ArrowDown' ? 1 : -1; var nextIndex; if (currentIndex === -1) { // No active item, start from first or last nextIndex = direction === 1 ? 0 : enabledItems.length - 1; } else { nextIndex = currentIndex + direction; } // Stop at boundaries if (nextIndex < 0 || nextIndex >= enabledItems.length) { return; } var nextItem = enabledItems[nextIndex]; setActiveItemId(nextItem.id); scrollItemIntoView(nextItem.id); // Single select: selection follows focus if (!multiple && onToggle) { onToggle(nextItem); } return; } // Enter/Space: toggle selection on active item if (onContainer && (e.key === 'Enter' || e.key === ' ')) { if (activeItemId !== null) { e.preventDefault(); e.stopPropagation(); var activeItem = items.find(item => item.id === activeItemId); if (activeItem && !activeItem.disabled && onToggle) { onToggle(activeItem); } } return; } }, [containerRef, items, activeItemId, scrollItemIntoView, multiple, onToggle]); var handleFocus = (0, _react.useCallback)(() => { // If focus came from mouse click, don't set active item if (isMouseFocusRef.current) { isMouseFocusRef.current = false; return; } // Restore saved item if returning from keyboard navigation if (lastActiveItemIdRef.current !== null) { var restoredId = lastActiveItemIdRef.current; setActiveItemId(restoredId); lastActiveItemIdRef.current = null; // Single select: also select the restored item if (!multiple && onToggle) { var restoredItem = items.find(item => item.id === restoredId); if (restoredItem && !restoredItem.disabled) { onToggle(restoredItem); } } return; } // Tab into container via keyboard - set first item as active var firstId = getFirstItemId(); if (firstId !== null) { setActiveItemId(firstId); // Single select: also select the first item if (!multiple && onToggle) { var firstItem = items.find(item => item.id === firstId); if (firstItem && !firstItem.disabled) { onToggle(firstItem); } } } }, [getFirstItemId, items, multiple, onToggle]); var handleBlur = (0, _react.useCallback)(() => { // Save and clear active item when container loses focus lastActiveItemIdRef.current = activeItemId; setActiveItemId(null); }, [activeItemId]); return { handleKeyDown, handleFocus, handleBlur, handleMouseDown, activeItemId }; }; var _default = exports.default = useScrollKeyboardNavigation; //# sourceMappingURL=useScrollKeyboardNavigation.js.map