mirror of
				https://github.com/ggml-org/llama.cpp.git
				synced 2025-10-31 08:51:55 +00:00 
			
		
		
		
	Improve Mobile UI for dialogs and action dropdowns (#16222)
* fix: Always show conversation item actions * feat: Improve Alert Dialog and Dialog mobile UI * feat: Add settings reset to default confirmation * fix: Close Edit dialog on save * chore: update webui build output * webui: implement proper z-index system and scroll management - Add CSS variable for centralized z-index control - Fix dropdown positioning with Settings dialog conflicts - Prevent external scroll interference with proper event handling - Clean up hardcoded z-index values for maintainable architecture * webui: ensured the settings dialog enforces dynamic viewport height on mobile while retaining existing desktop sizing overrides * feat: Use `dvh` instead of computed px height for dialogs max height on mobile * chore: update webui build output * feat: Improve Settings fields UI * chore: update webui build output * chore: update webui build output --------- Co-authored-by: Pascal <admin@serveurperso.com>
This commit is contained in:
		 Aleksander Grygier
					Aleksander Grygier
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							66bb7985c3
						
					
				
				
					commit
					3a2bdcda0b
				
			
										
											Binary file not shown.
										
									
								
							| @@ -39,6 +39,7 @@ | |||||||
| 	--sidebar-ring: oklch(0.708 0 0); | 	--sidebar-ring: oklch(0.708 0 0); | ||||||
| 	--code-background: oklch(0.225 0 0); | 	--code-background: oklch(0.225 0 0); | ||||||
| 	--code-foreground: oklch(0.875 0 0); | 	--code-foreground: oklch(0.875 0 0); | ||||||
|  | 	--layer-popover: 1000000; | ||||||
| } | } | ||||||
|  |  | ||||||
| .dark { | .dark { | ||||||
|   | |||||||
| @@ -362,7 +362,8 @@ | |||||||
|  |  | ||||||
| <Dialog.Root {open} onOpenChange={handleClose}> | <Dialog.Root {open} onOpenChange={handleClose}> | ||||||
| 	<Dialog.Content | 	<Dialog.Content | ||||||
| 		class="z-999999 flex h-[100vh] flex-col gap-0 rounded-none p-0 md:h-[64vh] md:rounded-lg" | 		class="z-999999 flex h-[100dvh] max-h-[100dvh] min-h-[100dvh] flex-col gap-0 rounded-none p-0 | ||||||
|  | 			md:h-[64vh] md:max-h-[64vh] md:min-h-0 md:rounded-lg" | ||||||
| 		style="max-width: 48rem;" | 		style="max-width: 48rem;" | ||||||
| 	> | 	> | ||||||
| 		<div class="flex flex-1 flex-col overflow-hidden md:flex-row"> | 		<div class="flex flex-1 flex-col overflow-hidden md:flex-row"> | ||||||
| @@ -441,7 +442,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
| 			<ScrollArea class="max-h-[calc(100vh-13.5rem)] flex-1"> | 			<ScrollArea class="max-h-[calc(100dvh-13.5rem)] flex-1 md:max-h-[calc(100vh-13.5rem)]"> | ||||||
| 				<div class="space-y-6 p-4 md:p-6"> | 				<div class="space-y-6 p-4 md:p-6"> | ||||||
| 					<div> | 					<div> | ||||||
| 						<div class="mb-6 flex hidden items-center gap-2 border-b border-border/30 pb-6 md:flex"> | 						<div class="mb-6 flex hidden items-center gap-2 border-b border-border/30 pb-6 md:flex"> | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ | |||||||
| 	import * as Select from '$lib/components/ui/select'; | 	import * as Select from '$lib/components/ui/select'; | ||||||
| 	import { Textarea } from '$lib/components/ui/textarea'; | 	import { Textarea } from '$lib/components/ui/textarea'; | ||||||
| 	import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config'; | 	import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config'; | ||||||
| 	import { IsMobile } from '$lib/hooks/is-mobile.svelte'; |  | ||||||
| 	import { supportsVision } from '$lib/stores/server.svelte'; | 	import { supportsVision } from '$lib/stores/server.svelte'; | ||||||
| 	import type { Component } from 'svelte'; | 	import type { Component } from 'svelte'; | ||||||
|  |  | ||||||
| @@ -17,8 +16,6 @@ | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props(); | 	let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props(); | ||||||
|  |  | ||||||
| 	let isMobile = $state(new IsMobile()); |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| {#each fields as field (field.key)} | {#each fields as field (field.key)} | ||||||
| @@ -33,7 +30,7 @@ | |||||||
| 				value={String(localConfig[field.key] ?? '')} | 				value={String(localConfig[field.key] ?? '')} | ||||||
| 				onchange={(e) => onConfigChange(field.key, e.currentTarget.value)} | 				onchange={(e) => onConfigChange(field.key, e.currentTarget.value)} | ||||||
| 				placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} | 				placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} | ||||||
| 				class={isMobile ? 'w-full' : 'max-w-md'} | 				class="w-full md:max-w-md" | ||||||
| 			/> | 			/> | ||||||
| 			{#if field.help || SETTING_CONFIG_INFO[field.key]} | 			{#if field.help || SETTING_CONFIG_INFO[field.key]} | ||||||
| 				<p class="mt-1 text-xs text-muted-foreground"> | 				<p class="mt-1 text-xs text-muted-foreground"> | ||||||
| @@ -50,7 +47,7 @@ | |||||||
| 				value={String(localConfig[field.key] ?? '')} | 				value={String(localConfig[field.key] ?? '')} | ||||||
| 				onchange={(e) => onConfigChange(field.key, e.currentTarget.value)} | 				onchange={(e) => onConfigChange(field.key, e.currentTarget.value)} | ||||||
| 				placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} | 				placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} | ||||||
| 				class={isMobile ? 'min-h-[100px] w-full' : 'min-h-[100px] max-w-2xl'} | 				class="min-h-[100px] w-full md:max-w-2xl" | ||||||
| 			/> | 			/> | ||||||
| 			{#if field.help || SETTING_CONFIG_INFO[field.key]} | 			{#if field.help || SETTING_CONFIG_INFO[field.key]} | ||||||
| 				<p class="mt-1 text-xs text-muted-foreground"> | 				<p class="mt-1 text-xs text-muted-foreground"> | ||||||
| @@ -78,7 +75,7 @@ | |||||||
| 					} | 					} | ||||||
| 				}} | 				}} | ||||||
| 			> | 			> | ||||||
| 				<Select.Trigger class={isMobile ? 'w-full' : 'max-w-md'}> | 				<Select.Trigger class="w-full md:w-auto md:max-w-md"> | ||||||
| 					<div class="flex items-center gap-2"> | 					<div class="flex items-center gap-2"> | ||||||
| 						{#if selectedOption?.icon} | 						{#if selectedOption?.icon} | ||||||
| 							{@const IconComponent = selectedOption.icon} | 							{@const IconComponent = selectedOption.icon} | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { Button } from '$lib/components/ui/button'; | 	import { Button } from '$lib/components/ui/button'; | ||||||
|  | 	import * as AlertDialog from '$lib/components/ui/alert-dialog'; | ||||||
|  |  | ||||||
| 	interface Props { | 	interface Props { | ||||||
| 		onReset?: () => void; | 		onReset?: () => void; | ||||||
| @@ -8,8 +9,15 @@ | |||||||
|  |  | ||||||
| 	let { onReset, onSave }: Props = $props(); | 	let { onReset, onSave }: Props = $props(); | ||||||
|  |  | ||||||
| 	function handleReset() { | 	let showResetDialog = $state(false); | ||||||
|  |  | ||||||
|  | 	function handleResetClick() { | ||||||
|  | 		showResetDialog = true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	function handleConfirmReset() { | ||||||
| 		onReset?.(); | 		onReset?.(); | ||||||
|  | 		showResetDialog = false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function handleSave() { | 	function handleSave() { | ||||||
| @@ -18,7 +26,23 @@ | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <div class="flex justify-between border-t border-border/30 p-6"> | <div class="flex justify-between border-t border-border/30 p-6"> | ||||||
| 	<Button variant="outline" onclick={handleReset}>Reset to default</Button> | 	<Button variant="outline" onclick={handleResetClick}>Reset to default</Button> | ||||||
|  |  | ||||||
| 	<Button onclick={handleSave}>Save settings</Button> | 	<Button onclick={handleSave}>Save settings</Button> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | <AlertDialog.Root bind:open={showResetDialog}> | ||||||
|  | 	<AlertDialog.Content> | ||||||
|  | 		<AlertDialog.Header> | ||||||
|  | 			<AlertDialog.Title>Reset Settings to Default</AlertDialog.Title> | ||||||
|  | 			<AlertDialog.Description> | ||||||
|  | 				Are you sure you want to reset all settings to their default values? This action cannot be | ||||||
|  | 				undone and will permanently remove all your custom configurations. | ||||||
|  | 			</AlertDialog.Description> | ||||||
|  | 		</AlertDialog.Header> | ||||||
|  | 		<AlertDialog.Footer> | ||||||
|  | 			<AlertDialog.Cancel>Cancel</AlertDialog.Cancel> | ||||||
|  | 			<AlertDialog.Action onclick={handleConfirmReset}>Reset to Default</AlertDialog.Action> | ||||||
|  | 		</AlertDialog.Footer> | ||||||
|  | 	</AlertDialog.Content> | ||||||
|  | </AlertDialog.Root> | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ | |||||||
| 		<Sidebar.GroupContent> | 		<Sidebar.GroupContent> | ||||||
| 			<Sidebar.Menu> | 			<Sidebar.Menu> | ||||||
| 				{#each filteredConversations as conversation (conversation.id)} | 				{#each filteredConversations as conversation (conversation.id)} | ||||||
| 					<Sidebar.MenuItem class="mb-1" onclick={handleMobileSidebarItemClick}> | 					<Sidebar.MenuItem class="mb-1"> | ||||||
| 						<ChatSidebarConversationItem | 						<ChatSidebarConversationItem | ||||||
| 							conversation={{ | 							conversation={{ | ||||||
| 								id: conversation.id, | 								id: conversation.id, | ||||||
| @@ -95,6 +95,7 @@ | |||||||
| 								lastModified: conversation.lastModified, | 								lastModified: conversation.lastModified, | ||||||
| 								currNode: conversation.currNode | 								currNode: conversation.currNode | ||||||
| 							}} | 							}} | ||||||
|  | 							{handleMobileSidebarItemClick} | ||||||
| 							isActive={currentChatId === conversation.id} | 							isActive={currentChatId === conversation.id} | ||||||
| 							onSelect={selectConversation} | 							onSelect={selectConversation} | ||||||
| 							onEdit={editConversation} | 							onEdit={editConversation} | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
| 	interface Props { | 	interface Props { | ||||||
| 		isActive?: boolean; | 		isActive?: boolean; | ||||||
| 		conversation: DatabaseConversation; | 		conversation: DatabaseConversation; | ||||||
|  | 		handleMobileSidebarItemClick?: () => void; | ||||||
| 		onDelete?: (id: string) => void; | 		onDelete?: (id: string) => void; | ||||||
| 		onEdit?: (id: string, name: string) => void; | 		onEdit?: (id: string, name: string) => void; | ||||||
| 		onSelect?: (id: string) => void; | 		onSelect?: (id: string) => void; | ||||||
| @@ -16,6 +17,7 @@ | |||||||
|  |  | ||||||
| 	let { | 	let { | ||||||
| 		conversation, | 		conversation, | ||||||
|  | 		handleMobileSidebarItemClick, | ||||||
| 		onDelete, | 		onDelete, | ||||||
| 		onEdit, | 		onEdit, | ||||||
| 		onSelect, | 		onSelect, | ||||||
| @@ -47,6 +49,7 @@ | |||||||
|  |  | ||||||
| 	function handleConfirmEdit() { | 	function handleConfirmEdit() { | ||||||
| 		if (!editedName.trim()) return; | 		if (!editedName.trim()) return; | ||||||
|  | 		showEditDialog = false; | ||||||
| 		onEdit?.(conversation.id, editedName); | 		onEdit?.(conversation.id, editedName); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -85,7 +88,12 @@ | |||||||
| 		: ''}" | 		: ''}" | ||||||
| 	onclick={handleSelect} | 	onclick={handleSelect} | ||||||
| > | > | ||||||
| 	<div class="text flex min-w-0 flex-1 items-center space-x-3"> | 	<!-- svelte-ignore a11y_click_events_have_key_events --> | ||||||
|  | 	<!-- svelte-ignore a11y_no_static_element_interactions --> | ||||||
|  | 	<div | ||||||
|  | 		class="text flex min-w-0 flex-1 items-center space-x-3" | ||||||
|  | 		onclick={handleMobileSidebarItemClick} | ||||||
|  | 	> | ||||||
| 		<div class="min-w-0 flex-1"> | 		<div class="min-w-0 flex-1"> | ||||||
| 			<p class="truncate text-sm font-medium">{conversation.name}</p> | 			<p class="truncate text-sm font-medium">{conversation.name}</p> | ||||||
|  |  | ||||||
| @@ -178,5 +186,10 @@ | |||||||
| 		&:is(:hover) :global([data-slot='dropdown-menu-trigger']) { | 		&:is(:hover) :global([data-slot='dropdown-menu-trigger']) { | ||||||
| 			opacity: 1; | 			opacity: 1; | ||||||
| 		} | 		} | ||||||
|  | 		@media (max-width: 768px) { | ||||||
|  | 			:global([data-slot='dropdown-menu-trigger']) { | ||||||
|  | 				opacity: 1 !important; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ | |||||||
| <DropdownMenu.Root bind:open> | <DropdownMenu.Root bind:open> | ||||||
| 	<DropdownMenu.Trigger | 	<DropdownMenu.Trigger | ||||||
| 		class="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md p-0 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground {triggerClass}" | 		class="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md p-0 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground {triggerClass}" | ||||||
|  | 		onclick={(e) => e.stopPropagation()} | ||||||
| 	> | 	> | ||||||
| 		{#if triggerTooltip} | 		{#if triggerTooltip} | ||||||
| 			<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}> | 			<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}> | ||||||
| @@ -53,7 +54,7 @@ | |||||||
| 		{/if} | 		{/if} | ||||||
| 	</DropdownMenu.Trigger> | 	</DropdownMenu.Trigger> | ||||||
|  |  | ||||||
| 	<DropdownMenu.Content {align} class="z-999 w-48"> | 	<DropdownMenu.Content {align} class="z-[999999] w-48"> | ||||||
| 		{#each actions as action, index (action.label)} | 		{#each actions as action, index (action.label)} | ||||||
| 			{#if action.separator && index > 0} | 			{#if action.separator && index > 0} | ||||||
| 				<DropdownMenu.Separator /> | 				<DropdownMenu.Separator /> | ||||||
|   | |||||||
| @@ -19,7 +19,15 @@ | |||||||
| 		bind:ref | 		bind:ref | ||||||
| 		data-slot="alert-dialog-content" | 		data-slot="alert-dialog-content" | ||||||
| 		class={cn( | 		class={cn( | ||||||
| 			'fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg', | 			'fixed z-[999999] grid w-full gap-4 border bg-background p-6 shadow-lg duration-200', | ||||||
|  | 			// Mobile: Bottom sheet behavior | ||||||
|  | 			'right-0 bottom-0 left-0 max-h-[100dvh] translate-x-0 translate-y-0 overflow-y-auto rounded-t-lg', | ||||||
|  | 			'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-full', | ||||||
|  | 			'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-full', | ||||||
|  | 			// Desktop: Centered dialog behavior | ||||||
|  | 			'sm:top-[50%] sm:right-auto sm:bottom-auto sm:left-[50%] sm:max-h-[100vh] sm:max-w-lg sm:translate-x-[-50%] sm:translate-y-[-50%] sm:rounded-lg', | ||||||
|  | 			'sm:data-[state=closed]:slide-out-to-bottom-0 sm:data-[state=closed]:zoom-out-95', | ||||||
|  | 			'sm:data-[state=open]:slide-in-from-bottom-0 sm:data-[state=open]:zoom-in-95', | ||||||
| 			className | 			className | ||||||
| 		)} | 		)} | ||||||
| 		{...restProps} | 		{...restProps} | ||||||
|   | |||||||
| @@ -13,7 +13,10 @@ | |||||||
| <div | <div | ||||||
| 	bind:this={ref} | 	bind:this={ref} | ||||||
| 	data-slot="alert-dialog-footer" | 	data-slot="alert-dialog-footer" | ||||||
| 	class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} | 	class={cn( | ||||||
|  | 		'mt-6 flex flex-row gap-2 sm:mt-0 sm:justify-end [&>*]:flex-1 sm:[&>*]:flex-none', | ||||||
|  | 		className | ||||||
|  | 	)} | ||||||
| 	{...restProps} | 	{...restProps} | ||||||
| > | > | ||||||
| 	{@render children?.()} | 	{@render children?.()} | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ | |||||||
| 		bind:ref | 		bind:ref | ||||||
| 		data-slot="dialog-content" | 		data-slot="dialog-content" | ||||||
| 		class={cn( | 		class={cn( | ||||||
| 			'fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border/30 bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg', | 			`fixed top-[50%] left-[50%] z-50 grid max-h-[100dvh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-lg border border-border/30 bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg md:max-h-[100vh]`, | ||||||
| 			className | 			className | ||||||
| 		)} | 		)} | ||||||
| 		{...restProps} | 		{...restProps} | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  | 	import { onDestroy, onMount } from 'svelte'; | ||||||
| 	import { Select as SelectPrimitive } from 'bits-ui'; | 	import { Select as SelectPrimitive } from 'bits-ui'; | ||||||
| 	import SelectScrollUpButton from './select-scroll-up-button.svelte'; | 	import SelectScrollUpButton from './select-scroll-up-button.svelte'; | ||||||
| 	import SelectScrollDownButton from './select-scroll-down-button.svelte'; | 	import SelectScrollDownButton from './select-scroll-down-button.svelte'; | ||||||
| @@ -14,6 +15,76 @@ | |||||||
| 	}: WithoutChild<SelectPrimitive.ContentProps> & { | 	}: WithoutChild<SelectPrimitive.ContentProps> & { | ||||||
| 		portalProps?: SelectPrimitive.PortalProps; | 		portalProps?: SelectPrimitive.PortalProps; | ||||||
| 	} = $props(); | 	} = $props(); | ||||||
|  |  | ||||||
|  | 	let cleanupInternalListeners: (() => void) | undefined; | ||||||
|  |  | ||||||
|  | 	onMount(() => { | ||||||
|  | 		const listenerOptions: AddEventListenerOptions = { passive: false }; | ||||||
|  |  | ||||||
|  | 		const blockOutsideWheel = (event: WheelEvent) => { | ||||||
|  | 			if (!ref) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const target = event.target as Node | null; | ||||||
|  |  | ||||||
|  | 			if (!target || !ref.contains(target)) { | ||||||
|  | 				event.preventDefault(); | ||||||
|  | 				event.stopPropagation(); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const blockOutsideTouchMove = (event: TouchEvent) => { | ||||||
|  | 			if (!ref) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const target = event.target as Node | null; | ||||||
|  |  | ||||||
|  | 			if (!target || !ref.contains(target)) { | ||||||
|  | 				event.preventDefault(); | ||||||
|  | 				event.stopPropagation(); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		document.addEventListener('wheel', blockOutsideWheel, listenerOptions); | ||||||
|  | 		document.addEventListener('touchmove', blockOutsideTouchMove, listenerOptions); | ||||||
|  |  | ||||||
|  | 		return () => { | ||||||
|  | 			document.removeEventListener('wheel', blockOutsideWheel, listenerOptions); | ||||||
|  | 			document.removeEventListener('touchmove', blockOutsideTouchMove, listenerOptions); | ||||||
|  | 		}; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	$effect(() => { | ||||||
|  | 		const element = ref; | ||||||
|  |  | ||||||
|  | 		cleanupInternalListeners?.(); | ||||||
|  |  | ||||||
|  | 		if (!element) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const stopWheelPropagation = (event: WheelEvent) => { | ||||||
|  | 			event.stopPropagation(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const stopTouchPropagation = (event: TouchEvent) => { | ||||||
|  | 			event.stopPropagation(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		element.addEventListener('wheel', stopWheelPropagation); | ||||||
|  | 		element.addEventListener('touchmove', stopTouchPropagation); | ||||||
|  |  | ||||||
|  | 		cleanupInternalListeners = () => { | ||||||
|  | 			element.removeEventListener('wheel', stopWheelPropagation); | ||||||
|  | 			element.removeEventListener('touchmove', stopTouchPropagation); | ||||||
|  | 		}; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	onDestroy(() => { | ||||||
|  | 		cleanupInternalListeners?.(); | ||||||
|  | 	}); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <SelectPrimitive.Portal {...portalProps}> | <SelectPrimitive.Portal {...portalProps}> | ||||||
| @@ -22,7 +93,7 @@ | |||||||
| 		{sideOffset} | 		{sideOffset} | ||||||
| 		data-slot="select-content" | 		data-slot="select-content" | ||||||
| 		class={cn( | 		class={cn( | ||||||
| 			'relative z-50 max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:-translate-x-1 data-[side=left]:slide-in-from-right-2 data-[side=right]:translate-x-1 data-[side=right]:slide-in-from-left-2 data-[side=top]:-translate-y-1 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', | 			'relative z-[var(--layer-popover,1000000)] max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:-translate-x-1 data-[side=left]:slide-in-from-right-2 data-[side=right]:translate-x-1 data-[side=right]:slide-in-from-left-2 data-[side=top]:-translate-y-1 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', | ||||||
| 			className | 			className | ||||||
| 		)} | 		)} | ||||||
| 		{...restProps} | 		{...restProps} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user