mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-29 06:32:52 +00:00
fix(a11y): arrow keys move through the input and integrations menus, and their items are menu items (#32940)
This commit is contained in:
parent
20f97c703f
commit
8ce855974f
3 changed files with 60 additions and 6 deletions
|
|
@ -146,6 +146,7 @@
|
|||
>
|
||||
{#if toolPermissionsEnabled}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
on:click={() => {
|
||||
tab = 'tool_permissions';
|
||||
|
|
@ -194,6 +195,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -220,6 +222,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -245,6 +248,7 @@
|
|||
</Tooltip>
|
||||
|
||||
<button
|
||||
role="menuitem"
|
||||
type="button"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
on:click={async () => {
|
||||
|
|
@ -276,6 +280,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!webUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -301,6 +306,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -334,6 +340,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -365,6 +372,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -395,6 +403,7 @@
|
|||
className="w-full"
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -419,6 +428,7 @@
|
|||
{#if fileUploadEnabled}
|
||||
{#if $config?.features?.enable_google_drive_integration}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -462,6 +472,7 @@
|
|||
|
||||
{#if $config?.features?.enable_onedrive_integration && ($config?.features?.enable_onedrive_personal || $config?.features?.enable_onedrive_business)}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
|
|
@ -571,6 +582,7 @@
|
|||
{:else if tab === 'tool_permissions'}
|
||||
<div class="flex max-h-72 flex-col overflow-hidden" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full shrink-0 justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -589,6 +601,8 @@
|
|||
{#each toolApprovalModes as mode}
|
||||
<Tooltip content={$i18n.t(mode.description)} className="w-full">
|
||||
<button
|
||||
role="menuitemradio"
|
||||
aria-checked={toolApprovalMode === mode.value}
|
||||
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
on:click={() => {
|
||||
toolApprovalMode = mode.value;
|
||||
|
|
@ -620,6 +634,7 @@
|
|||
{:else if tab === 'knowledge'}
|
||||
<div class="flex max-h-72 flex-col overflow-hidden" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full shrink-0 justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -639,6 +654,7 @@
|
|||
{:else if tab === 'notes'}
|
||||
<div class="flex max-h-72 flex-col overflow-hidden" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full shrink-0 justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -658,6 +674,7 @@
|
|||
{:else if tab === 'files'}
|
||||
<div class="flex max-h-72 flex-col overflow-hidden" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full shrink-0 justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -677,6 +694,7 @@
|
|||
{:else if tab === 'chats'}
|
||||
<div class="flex max-h-72 flex-col overflow-hidden" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full shrink-0 justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -696,6 +714,7 @@
|
|||
{:else if tab === 'microsoft_onedrive'}
|
||||
<div in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = '';
|
||||
|
|
@ -712,6 +731,7 @@
|
|||
|
||||
{#if $config?.features?.enable_onedrive_personal}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -727,6 +747,7 @@
|
|||
|
||||
{#if $config?.features?.enable_onedrive_business}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
|
|||
|
|
@ -315,6 +315,7 @@
|
|||
{#if tools}
|
||||
{#if Object.keys(tools).length > 0}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = 'tools';
|
||||
|
|
@ -337,6 +338,7 @@
|
|||
|
||||
{#if skills && Object.keys(skills).length > 0}
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
tab = 'skills';
|
||||
|
|
@ -375,7 +377,8 @@
|
|||
>
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={selectedFilterIds.includes(filter.id)}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={selectedFilterIds.includes(filter.id)}
|
||||
on:click={() => {
|
||||
if (selectedFilterIds.includes(filter.id)) {
|
||||
selectedFilterIds = selectedFilterIds.filter((id) => id !== filter.id);
|
||||
|
|
@ -447,7 +450,8 @@
|
|||
<Tooltip content={$i18n.t('Search the internet')} placement="top-start">
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={webSearchEnabled}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={webSearchEnabled}
|
||||
on:click={() => {
|
||||
webSearchEnabled = !webSearchEnabled;
|
||||
// Left open, this menu would swallow the first click on the confirmation dialog
|
||||
|
|
@ -478,7 +482,8 @@
|
|||
<Tooltip content={$i18n.t('Generate an image')} placement="top-start">
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={imageGenerationEnabled}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={imageGenerationEnabled}
|
||||
on:click={() => {
|
||||
imageGenerationEnabled = !imageGenerationEnabled;
|
||||
}}
|
||||
|
|
@ -504,7 +509,8 @@
|
|||
<Tooltip content={$i18n.t('Execute code for analysis')} placement="top-start">
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={codeInterpreterEnabled}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={codeInterpreterEnabled}
|
||||
on:click={() => {
|
||||
codeInterpreterEnabled = !codeInterpreterEnabled;
|
||||
}}
|
||||
|
|
@ -529,6 +535,7 @@
|
|||
{:else if tab === 'tools' && tools}
|
||||
<div class="flex max-h-72 min-h-0 flex-col gap-0.5" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
toolQuery = '';
|
||||
|
|
@ -555,7 +562,10 @@
|
|||
{#each toolIds as toolId}
|
||||
<button
|
||||
class="relative flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={(tools?.[toolId]?.authenticated ?? true)
|
||||
role={(tools?.[toolId]?.authenticated ?? true)
|
||||
? 'menuitemcheckbox'
|
||||
: 'menuitem'}
|
||||
aria-checked={(tools?.[toolId]?.authenticated ?? true)
|
||||
? selectedToolIds.includes(toolId)
|
||||
: undefined}
|
||||
on:click={async (e) => {
|
||||
|
|
@ -663,6 +673,7 @@
|
|||
{:else if tab === 'skills' && skills}
|
||||
<div class="flex max-h-72 min-h-0 flex-col gap-0.5" in:fly={{ x: 20, duration: 150 }}>
|
||||
<button
|
||||
role="menuitem"
|
||||
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
on:click={() => {
|
||||
skillQuery = '';
|
||||
|
|
@ -689,7 +700,8 @@
|
|||
{#each skillIds as skillId}
|
||||
<button
|
||||
class="relative flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||
aria-pressed={selectedSkillIds.includes(skillId)}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={selectedSkillIds.includes(skillId)}
|
||||
on:click={async () => {
|
||||
await toggleSkill(skillId);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -326,6 +326,27 @@
|
|||
if (event.key === 'Escape' && show) {
|
||||
closeDropdown();
|
||||
}
|
||||
|
||||
if (
|
||||
['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) &&
|
||||
event.target instanceof HTMLElement &&
|
||||
event.target.matches('[role^="menuitem"]') &&
|
||||
contentEl?.contains(event.target)
|
||||
) {
|
||||
const items = [
|
||||
...contentEl.querySelectorAll<HTMLElement>('[role^="menuitem"]:not([disabled])')
|
||||
].filter((item) => item.getClientRects().length > 0);
|
||||
const index = items.indexOf(event.target);
|
||||
const nextIndex =
|
||||
event.key === 'Home'
|
||||
? 0
|
||||
: event.key === 'End'
|
||||
? items.length - 1
|
||||
: (index + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
||||
|
||||
event.preventDefault();
|
||||
items[nextIndex]?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/** Close the dropdown programmatically */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue