fix(InputMenu/Select/SelectMenu): manual viewport to display scrollbars

Resolves #4069
This commit is contained in:
Benjamin Canac
2025-05-23 17:41:30 +02:00
parent dcf34a7ac2
commit f95abf8d1d
11 changed files with 497 additions and 3377 deletions

View File

@@ -172,7 +172,7 @@ export interface InputMenuSlots<
<script setup lang="ts" generic="T extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false">
import { computed, ref, toRef, onMounted, toRaw } from 'vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
import { defu } from 'defu'
import { isEqual } from 'ohash/utils'
import { reactivePick, createReusableTemplate } from '@vueuse/core'
@@ -490,7 +490,7 @@ defineExpose({
</slot>
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
@@ -541,7 +541,7 @@ defineExpose({
</ComboboxGroup>
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport>
</div>
<slot name="content-bottom" />

View File

@@ -135,7 +135,7 @@ export interface SelectSlots<
<script setup lang="ts" generic="T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
import { computed, toRef } from 'vue'
import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectViewport, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'reka-ui'
import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectLabel, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectSeparator, useForwardPropsEmits } from 'reka-ui'
import { defu } from 'defu'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
@@ -270,7 +270,7 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
<SelectContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<slot name="content-top" />
<SelectViewport :class="ui.viewport({ class: props.ui?.viewport })">
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
<SelectGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
<SelectLabel v-if="isSelectItem(item) && item.type === 'label'" :class="ui.label({ class: [props.ui?.label, item.ui?.label, item.class] })">
@@ -317,7 +317,7 @@ function isSelectItem(item: SelectItem): item is SelectItemBase {
</SelectItem>
</template>
</SelectGroup>
</SelectViewport>
</div>
<slot name="content-bottom" />

View File

@@ -166,7 +166,7 @@ export interface SelectMenuSlots<
<script setup lang="ts" generic="T extends ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false">
import { computed, toRef, toRaw } from 'vue'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope, useForwardPropsEmits, useFilter } from 'reka-ui'
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope, useForwardPropsEmits, useFilter } from 'reka-ui'
import { defu } from 'defu'
import { reactivePick, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports'
@@ -417,7 +417,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
</slot>
</ComboboxEmpty>
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
<div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
@@ -468,7 +468,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
</ComboboxGroup>
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport>
</div>
<slot name="content-bottom" />
</FocusScope>

View File

@@ -8,8 +8,8 @@ export default (options: Required<ModuleOptions>) => {
base: () => ['rounded-md', options.theme.transitions && 'transition-colors'],
trailing: 'group absolute inset-y-0 end-0 flex items-center disabled:cursor-not-allowed disabled:opacity-75',
arrow: 'fill-default',
content: 'max-h-60 w-(--reka-combobox-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-combobox-content-transform-origin) pointer-events-auto',
viewport: 'divide-y divide-default scroll-py-1',
content: 'max-h-60 w-(--reka-combobox-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-combobox-content-transform-origin) pointer-events-auto flex flex-col',
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate',
empty: 'py-2 text-center text-sm text-muted',
label: 'font-semibold text-highlighted',

View File

@@ -11,8 +11,8 @@ export default (options: Required<ModuleOptions>) => {
value: 'truncate pointer-events-none',
placeholder: 'truncate text-dimmed',
arrow: 'fill-default',
content: 'max-h-60 w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-select-content-transform-origin) pointer-events-auto',
viewport: 'divide-y divide-default scroll-py-1',
content: 'max-h-60 w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-select-content-transform-origin) pointer-events-auto flex flex-col',
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate',
empty: 'py-2 text-center text-sm text-muted',
label: 'font-semibold text-highlighted',