@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
177 lines (176 loc) • 6.43 kB
JavaScript
"use client";
import React, { useCallback, useRef, useState } from 'react';
import clsx from 'clsx';
import { useMenuContext } from "./MenuContext.js";
import useIsomorphicLayoutEffect from "../../shared/helpers/useIsomorphicLayoutEffect.js";
import { jsx as _jsx } from "react/jsx-runtime";
export default function MenuList(props) {
const {
children,
className,
maxVisibleListItems,
style,
...rest
} = props;
const context = useMenuContext();
const ulRef = useRef(null);
const hasValidMaxVisibleListItems = typeof maxVisibleListItems === 'number' && Number.isFinite(maxVisibleListItems) && maxVisibleListItems > 0;
const fallbackMaxHeight = hasValidMaxVisibleListItems ? `calc(var(--menu-action-min-height, 2.5rem) * ${maxVisibleListItems} + var(--menu-content-padding, 0.25rem) * 2)` : undefined;
const [measuredMaxHeight, setMeasuredMaxHeight] = useState(undefined);
const measureMaxHeight = useCallback(() => {
if (!hasValidMaxVisibleListItems || style !== null && style !== void 0 && style.maxHeight) {
setMeasuredMaxHeight(undefined);
return;
}
const height = getVisibleMenuItemsHeight(ulRef.current, maxVisibleListItems);
setMeasuredMaxHeight(height ? `${height}px` : undefined);
}, [hasValidMaxVisibleListItems, maxVisibleListItems, style === null || style === void 0 ? void 0 : style.maxHeight]);
useIsomorphicLayoutEffect(() => {
measureMaxHeight();
}, [children, measureMaxHeight]);
useIsomorphicLayoutEffect(() => {
if (!hasValidMaxVisibleListItems || style !== null && style !== void 0 && style.maxHeight) {
return undefined;
}
window.addEventListener('resize', measureMaxHeight);
return () => {
window.removeEventListener('resize', measureMaxHeight);
};
}, [hasValidMaxVisibleListItems, measureMaxHeight, style === null || style === void 0 ? void 0 : style.maxHeight]);
useIsomorphicLayoutEffect(() => {
if (context !== null && context !== void 0 && context.menuRef) {
context.menuRef.current = ulRef.current;
}
});
const getNavigableItems = useCallback(() => {
const menuEl = ulRef.current;
if (!menuEl) {
return [];
}
return Array.from(menuEl.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])'));
}, []);
const focusByDomOrder = useCallback(element => {
if (!context) {
return;
}
element.focus({
preventScroll: true
});
const refIndex = context.itemRefs.current.findIndex(r => (r === null || r === void 0 ? void 0 : r.current) === element);
if (refIndex !== -1) {
context.setActiveIndex(refIndex);
}
}, [context]);
const handleKeyDown = useCallback(event => {
if (!context) {
return;
}
const items = getNavigableItems();
if (items.length === 0) {
return;
}
let currentIdx = items.indexOf(document.activeElement);
if (currentIdx === -1 && context.activeIndex >= 0) {
const activeRef = context.itemRefs.current[context.activeIndex];
if (activeRef !== null && activeRef !== void 0 && activeRef.current) {
currentIdx = items.indexOf(activeRef.current);
}
}
switch (event.key) {
case 'ArrowDown':
{
event.preventDefault();
event.stopPropagation();
if (currentIdx === -1) {
focusByDomOrder(items[0]);
} else {
focusByDomOrder(items[(currentIdx + 1) % items.length]);
}
break;
}
case 'ArrowUp':
{
event.preventDefault();
event.stopPropagation();
if (currentIdx === -1) {
focusByDomOrder(items[items.length - 1]);
} else {
focusByDomOrder(items[(currentIdx - 1 + items.length) % items.length]);
}
break;
}
case 'Home':
case 'PageUp':
{
event.preventDefault();
event.stopPropagation();
focusByDomOrder(items[0]);
break;
}
case 'End':
case 'PageDown':
{
event.preventDefault();
event.stopPropagation();
focusByDomOrder(items[items.length - 1]);
break;
}
case 'Escape':
case 'Tab':
{
break;
}
default:
{
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
const char = event.key.toLowerCase();
const startIdx = currentIdx === -1 ? 0 : (currentIdx + 1) % items.length;
for (let i = 0; i < items.length; i++) {
var _items$idx$textConten;
const idx = (startIdx + i) % items.length;
const text = (_items$idx$textConten = items[idx].textContent) === null || _items$idx$textConten === void 0 ? void 0 : _items$idx$textConten.trim().toLowerCase();
if (text !== null && text !== void 0 && text.startsWith(char)) {
focusByDomOrder(items[idx]);
break;
}
}
}
}
}
}, [context, getNavigableItems, focusByDomOrder]);
const resolvedMaxHeight = style !== null && style !== void 0 && style.maxHeight ? undefined : measuredMaxHeight || fallbackMaxHeight;
const listStyle = {
...(resolvedMaxHeight ? {
maxHeight: resolvedMaxHeight,
overflowY: 'auto'
} : null),
...style
};
return _jsx("ul", {
ref: ulRef,
role: "menu",
tabIndex: -1,
className: clsx("dnb-menu__list dnb-no-focus", className),
style: Object.keys(listStyle).length > 0 ? listStyle : undefined,
onKeyDown: handleKeyDown,
...rest,
children: children
});
}
function getVisibleMenuItemsHeight(ulElement, maxVisibleListItems) {
if (!ulElement) {
return null;
}
const items = Array.from(ulElement.children).filter(element => element instanceof HTMLElement);
const firstVisibleItem = items[0];
const lastVisibleItem = items[maxVisibleListItems - 1];
if (!firstVisibleItem || !lastVisibleItem) {
return null;
}
const contentHeight = Math.ceil(lastVisibleItem.offsetTop + lastVisibleItem.offsetHeight - firstVisibleItem.offsetTop);
const computedStyle = getComputedStyle(ulElement);
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
return contentHeight + paddingTop + paddingBottom;
}
//# sourceMappingURL=MenuList.js.map