@wix/design-system
Version:
@wix/design-system
151 lines (138 loc) • 5.21 kB
JavaScript
"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