From cfd547858d1810cde16c3bb0170a9605ea862e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=87=A4?= Date: Mon, 14 Sep 2026 18:19:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(menu):=20=E6=94=AF=E6=8C=81=E5=AD=90?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E8=87=AA=E7=AE=A1=E6=BB=9A=E5=8A=A8=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E5=92=8C=E7=84=A6=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dropdown-menu/DropdownMenuSubContent.vue | 24 +++++++++++++++---- .../menu-scroll-area/MenuScrollArea.vue | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/dropdown-menu/DropdownMenuSubContent.vue b/src/components/dropdown-menu/DropdownMenuSubContent.vue index a52d660..7b08df1 100644 --- a/src/components/dropdown-menu/DropdownMenuSubContent.vue +++ b/src/components/dropdown-menu/DropdownMenuSubContent.vue @@ -12,14 +12,20 @@ import MenuScrollArea from '../menu-scroll-area/MenuScrollArea.vue' import { useSubmenuAlignment } from '../../lib/useSubmenuAlignment' import { cn } from '#/lib/utils' -const props = defineProps() +const props = withDefaults(defineProps(), { scrollable: true }) const emits = defineEmits() -const delegatedProps = reactiveOmit(props, 'class') +const delegatedProps = reactiveOmit(props, 'class', 'scrollable') const forwarded = useForwardPropsEmits(delegatedProps, emits) const menuArea = ref>() -const currentElement = computed(() => menuArea.value?.viewportElement?.closest('[data-slot="dropdown-menu-sub-content"]') ?? undefined) +const customBody = ref() +const currentElement = computed(() => (menuArea.value?.viewportElement ?? customBody.value) + ?.closest('[data-slot="dropdown-menu-sub-content"]') ?? undefined) const { alignOffset, ready } = useSubmenuAlignment(currentElement) // Start the entrance on a fresh frame after mounting and placement have settled. // Otherwise a long list can consume the animation before its first visible paint. @@ -43,9 +49,19 @@ watch(ready, (value, _, cleanup) => { :style="{ visibility: ready ? undefined : 'hidden' }" :class="cn(menuWidthClass, menuContentClass, menuAnchoredMotionClass, 'flex min-h-0 flex-col overflow-hidden max-h-(--reka-dropdown-menu-content-available-height) origin-(--reka-dropdown-menu-content-transform-origin)', props.class, !motionReady && 'animate-none! opacity-0')" > - + +
+ +
diff --git a/src/components/menu-scroll-area/MenuScrollArea.vue b/src/components/menu-scroll-area/MenuScrollArea.vue index 8de53b0..06f071f 100644 --- a/src/components/menu-scroll-area/MenuScrollArea.vue +++ b/src/components/menu-scroll-area/MenuScrollArea.vue @@ -40,7 +40,7 @@ const { fadeTop, fadeBottom } = useScrollFade(element) '--scroll-fade-top': fadeTop ? '16px' : '0px', '--scroll-fade-bottom': fadeBottom ? '16px' : '0px', }" - :tabindex="-1" + :tabindex="props.viewportAttrs?.tabindex ?? -1" >
From 5fbc8e5914a1e891401b099bdf1e03fd65fe98d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=87=A4?= Date: Mon, 14 Sep 2026 18:31:37 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(menu):=20=E6=8C=89=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E4=B8=AD=E5=BF=83=E5=AF=B9=E9=BD=90=E5=AD=90?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/useSubmenuAlignment.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/useSubmenuAlignment.ts b/src/lib/useSubmenuAlignment.ts index 13b5bb5..739eb26 100644 --- a/src/lib/useSubmenuAlignment.ts +++ b/src/lib/useSubmenuAlignment.ts @@ -11,7 +11,11 @@ export function useSubmenuAlignment(content: Readonly { - const first = element.querySelector('[role^="menuitem"]') + // Virtual listboxes may unmount their first option while scrolling. Only + // anchor to logical row one, never to the first currently mounted row. + const first = element.querySelector( + '[role^="menuitem"], [role="option"][aria-posinset="1"], [role="option"]:not([aria-posinset])', + ) if (first && element.offsetWidth) { const bounds = element.getBoundingClientRect() // Opening scale animation must not influence layout measurements.