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"> <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 { 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 { defu } from 'defu'
import { isEqual } from 'ohash/utils' import { isEqual } from 'ohash/utils'
import { reactivePick, createReusableTemplate } from '@vueuse/core' import { reactivePick, createReusableTemplate } from '@vueuse/core'
@@ -490,7 +490,7 @@ defineExpose({
</slot> </slot>
</ComboboxEmpty> </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'" /> <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })"> <ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
@@ -541,7 +541,7 @@ defineExpose({
</ComboboxGroup> </ComboboxGroup>
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" /> <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport> </div>
<slot name="content-bottom" /> <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"> <script setup lang="ts" generic="T extends ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
import { computed, toRef } from 'vue' 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 { defu } from 'defu'
import { reactivePick } from '@vueuse/core' import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports' 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"> <SelectContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
<slot name="content-top" /> <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 })"> <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}`"> <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] })"> <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> </SelectItem>
</template> </template>
</SelectGroup> </SelectGroup>
</SelectViewport> </div>
<slot name="content-bottom" /> <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"> <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 { 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 { defu } from 'defu'
import { reactivePick, createReusableTemplate } from '@vueuse/core' import { reactivePick, createReusableTemplate } from '@vueuse/core'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
@@ -417,7 +417,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
</slot> </slot>
</ComboboxEmpty> </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'" /> <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })"> <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> </ComboboxGroup>
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" /> <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
</ComboboxViewport> </div>
<slot name="content-bottom" /> <slot name="content-bottom" />
</FocusScope> </FocusScope>

View File

@@ -8,8 +8,8 @@ export default (options: Required<ModuleOptions>) => {
base: () => ['rounded-md', options.theme.transitions && 'transition-colors'], 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', trailing: 'group absolute inset-y-0 end-0 flex items-center disabled:cursor-not-allowed disabled:opacity-75',
arrow: 'fill-default', 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', 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: 'divide-y divide-default scroll-py-1', viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate', group: 'p-1 isolate',
empty: 'py-2 text-center text-sm text-muted', empty: 'py-2 text-center text-sm text-muted',
label: 'font-semibold text-highlighted', label: 'font-semibold text-highlighted',

View File

@@ -11,8 +11,8 @@ export default (options: Required<ModuleOptions>) => {
value: 'truncate pointer-events-none', value: 'truncate pointer-events-none',
placeholder: 'truncate text-dimmed', placeholder: 'truncate text-dimmed',
arrow: 'fill-default', 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', 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: 'divide-y divide-default scroll-py-1', viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate', group: 'p-1 isolate',
empty: 'py-2 text-center text-sm text-muted', empty: 'py-2 text-center text-sm text-muted',
label: 'font-semibold text-highlighted', label: 'font-semibold text-highlighted',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff