npm package diff

Package: @chakra-ui/react

Versions: 2.10.1 - 2.10.2

File: package/dist/esm/menu/use-menu.mjs

Index: package/dist/esm/menu/use-menu.mjs
===================================================================
--- package/dist/esm/menu/use-menu.mjs
+++ package/dist/esm/menu/use-menu.mjs
@@ -1,8 +1,8 @@
 'use client';
-import { useDisclosure, useOutsideClick, useUpdateEffect, useFocusOnHide, useAnimationState, mergeRefs, useControllableState } from '@chakra-ui/hooks';
+import { useDisclosure, useOutsideClick, useFocusOnHide, useAnimationState, useIds, useUpdateEffect, mergeRefs, useControllableState } from '@chakra-ui/hooks';
 import { createContext, dataAttr, callAllHandlers, lazyDisclosure, getValidChildren } from '@chakra-ui/utils';
-import { useRef, useCallback, useState, useEffect, useId, cloneElement, useMemo } from 'react';
+import { useRef, useCallback, useState, useEffect, useId, cloneElement } from 'react';
 import { getNextItemFromSearch } from './get-next-item-from-search.mjs';
 import { useShortcut } from './use-shortcut.mjs';
 import { createDescendantContext } from '../descendant/use-descendant.mjs';
 import { usePopper } from '../popper/use-popper.mjs';
@@ -17,15 +17,8 @@
 const [MenuProvider, useMenuContext] = createContext({
   strict: false,
   name: "MenuContext"
 });
-function useIds(idProp, ...prefixes) {
-  const reactId = useId();
-  const id = idProp || reactId;
-  return useMemo(() => {
-    return prefixes.map((prefix) => `${prefix}-${id}`);
-  }, [id, prefixes]);
-}
 function getOwnerDocument(node) {
   return node?.ownerDocument ?? document;
 }
 function isActiveElement(element) {
@@ -51,8 +44,9 @@
     ...popperProps
   } = props;
   const menuRef = useRef(null);
   const buttonRef = useRef(null);
+  const scrollIntoViewRef = useRef(true);
   const descendants = useMenuDescendants();
   const focusMenu = useCallback(() => {
     requestAnimationFrame(() => {
       menuRef.current?.focus({ preventScroll: false });
@@ -61,8 +55,10 @@
   const focusFirstItem = useCallback(() => {
     const id2 = setTimeout(() => {
       if (initialFocusRef) {
         initialFocusRef.current?.focus();
+      } else if (!descendants.count()) {
+        menuRef.current?.focus({ preventScroll: false });
       } else {
         const first = descendants.firstEnabled();
         if (first)
           setFocusedIndex(first.index);
@@ -71,11 +67,15 @@
     timeoutIds.current.add(id2);
   }, [descendants, initialFocusRef]);
   const focusLastItem = useCallback(() => {
     const id2 = setTimeout(() => {
-      const last = descendants.lastEnabled();
-      if (last)
-        setFocusedIndex(last.index);
+      if (!descendants.count()) {
+        menuRef.current?.focus({ preventScroll: false });
+      } else {
+        const last = descendants.lastEnabled();
+        if (last)
+          setFocusedIndex(last.index);
+      }
     });
     timeoutIds.current.add(id2);
   }, [descendants]);
   const onOpenInternal = useCallback(() => {
@@ -108,13 +108,8 @@
     placement,
     direction
   });
   const [focusedIndex, setFocusedIndex] = useState(-1);
-  useUpdateEffect(() => {
-    if (!isOpen) {
-      setFocusedIndex(-1);
-    }
-  }, [isOpen]);
   useFocusOnHide(menuRef, {
     focusRef: buttonRef,
     visible: isOpen,
     shouldFocus: true
@@ -132,13 +127,33 @@
       ids.forEach((id2) => clearTimeout(id2));
       ids.clear();
     };
   }, []);
+  useUpdateEffect(() => {
+    if (isOpen)
+      return;
+    setFocusedIndex(-1);
+    menuRef.current?.scrollTo(0, 0);
+  }, [isOpen]);
+  useUpdateEffect(() => {
+    if (!isOpen)
+      return;
+    if (focusedIndex === -1) {
+      focusMenu();
+    }
+  }, [focusedIndex, isOpen]);
+  useEffect(() => {
+    if (!isOpen)
+      return;
+    const item = descendants.item(focusedIndex);
+    item?.node?.focus({ preventScroll: !scrollIntoViewRef.current });
+  }, [descendants, focusedIndex, isOpen]);
   const openAndFocusFirstItem = useCallback(() => {
     onOpen();
     focusFirstItem();
   }, [focusFirstItem, onOpen]);
   const openAndFocusLastItem = useCallback(() => {
+    scrollIntoViewRef.current = true;
     onOpen();
     focusLastItem();
   }, [onOpen, focusLastItem]);
   const refocus = useCallback(() => {
@@ -147,11 +162,10 @@
     const shouldRefocus = isOpen && !hasFocusWithin;
     if (!shouldRefocus)
       return;
     const node = descendants.item(focusedIndex)?.node;
-    node?.focus({ preventScroll: true });
+    node?.focus({ preventScroll: !scrollIntoViewRef.current });
   }, [isOpen, focusedIndex, descendants]);
-  const rafId = useRef(null);
   return {
     openAndFocusMenu,
     openAndFocusFirstItem,
     openAndFocusLastItem,
@@ -176,14 +190,20 @@
     setFocusedIndex,
     isLazy,
     lazyBehavior,
     initialFocusRef,
-    rafId
+    scrollIntoViewRef
   };
 }
 function useMenuButton(props = {}, externalRef = null) {
   const menu = useMenuContext();
-  const { onToggle, popper, openAndFocusFirstItem, openAndFocusLastItem } = menu;
+  const {
+    onToggle,
+    popper,
+    openAndFocusFirstItem,
+    openAndFocusLastItem,
+    scrollIntoViewRef
+  } = menu;
   const onKeyDown = useCallback(
     (event) => {
       const eventKey = event.key;
       const keyMap = {
@@ -192,14 +212,15 @@
         ArrowUp: openAndFocusLastItem
       };
       const action = keyMap[eventKey];
       if (action) {
+        scrollIntoViewRef.current = true;
         event.preventDefault();
         event.stopPropagation();
         action(event);
       }
     },
-    [openAndFocusFirstItem, openAndFocusLastItem]
+    [openAndFocusFirstItem, openAndFocusLastItem, scrollIntoViewRef]
   );
   return {
     ...props,
     ref: mergeRefs(menu.buttonRef, externalRef, popper.referenceRef),
@@ -230,8 +251,9 @@
     onClose,
     menuId,
     isLazy,
     lazyBehavior,
+    scrollIntoViewRef,
     unstable__animationState: animated
   } = menu;
   const descendants = useMenuDescendantsContext();
   const createTypeaheadHandler = useShortcut({
@@ -248,14 +270,16 @@
           event2.stopPropagation();
           onClose();
         },
         ArrowDown: () => {
-          const next = descendants.nextEnabled(focusedIndex);
+          scrollIntoViewRef.current = true;
+          const next = descendants.nextEnabled(focusedIndex) ?? descendants.firstEnabled();
           if (next)
             setFocusedIndex(next.index);
         },
         ArrowUp: () => {
-          const prev = descendants.prevEnabled(focusedIndex);
+          scrollIntoViewRef.current = true;
+          const prev = descendants.prevEnabled(focusedIndex) ?? descendants.firstEnabled();
           if (prev)
             setFocusedIndex(prev.index);
         }
       };
@@ -285,9 +309,10 @@
       descendants,
       focusedIndex,
       createTypeaheadHandler,
       onClose,
-      setFocusedIndex
+      setFocusedIndex,
+      scrollIntoViewRef
     ]
   );
   const hasBeenOpened = useRef(false);
   if (isOpen) {
@@ -342,12 +367,10 @@
     setFocusedIndex,
     focusedIndex,
     closeOnSelect: menuCloseOnSelect,
     onClose,
-    menuRef,
-    isOpen,
     menuId,
-    rafId
+    scrollIntoViewRef
   } = menu;
   const ref = useRef(null);
   const id = `${menuId}-menuitem-${useId()}`;
   const { index, register } = useMenuDescendant({
@@ -357,11 +380,12 @@
     (event) => {
       onMouseEnterProp?.(event);
       if (isDisabled)
         return;
+      scrollIntoViewRef.current = false;
       setFocusedIndex(index);
     },
-    [setFocusedIndex, index, isDisabled, onMouseEnterProp]
+    [setFocusedIndex, index, isDisabled, onMouseEnterProp, scrollIntoViewRef]
   );
   const onMouseMove = useCallback(
     (event) => {
       onMouseMoveProp?.(event);
@@ -398,29 +422,8 @@
     },
     [setFocusedIndex, onFocusProp, index]
   );
   const isFocused = index === focusedIndex;
-  const trulyDisabled = isDisabled && !isFocusable;
-  useUpdateEffect(() => {
-    if (!isOpen)
-      return;
-    if (isFocused && !trulyDisabled && ref.current) {
-      if (rafId.current) {
-        cancelAnimationFrame(rafId.current);
-      }
-      rafId.current = requestAnimationFrame(() => {
-        ref.current?.focus({ preventScroll: true });
-        rafId.current = null;
-      });
-    } else if (menuRef.current && !isActiveElement(menuRef.current)) {
-      menuRef.current.focus({ preventScroll: true });
-    }
-    return () => {
-      if (rafId.current) {
-        cancelAnimationFrame(rafId.current);
-      }
-    };
-  }, [isFocused, trulyDisabled, menuRef, isOpen]);
   const clickableProps = useClickable({
     onClick,
     onFocus,
     onMouseEnter,