feat(web): Add keyboard shortcut selection on grid (#16713)

* 15712: Added keyboard shortcuts for opening add to album modal and highlighting/selecting an album to add to.

* 15712: Re-factored logic from template code into script. Extracted new album button into separate cmponent.

* 15712: Document new keyboard shortucts now that they work everywhere.

* 15712: Extract some constants/helper functions.

* 15712: Missing comma.

* 15712: Pulled logic out into separate unit testable class.

* 15712: Added a unit test.

* 15712: Move the modal back up to keep the github PR happy.

* 15712: PR feedback - renamed typescript files and switch to class bind directive.

* 15712:Move selection modal into correct package.

* 15712: Better naming of module and files.

* 15712: Add asset highlight using arrow keys.

* 15172: Add escape behaviour everywhere.

* 15712: Don't allow highlighting past start or end.

* 15712: Clear the highlight on changes to the component state.

* 15712: Use focus to track highlighted element.

* 15712: Rename highlight -> focussed.

* 15712: Better naming.

* 15712: Cleanup.

* 15712: Cleanup & simplify.

* 15712: bugfix for clicking on button.

* 15712: Cleanup.

* 15712: Rollback unnecessary changes.

* 15712: Add unit test.

* 15712: Add thumbnail unit test.

* 15712: Prettier.

* 15712: Fix merge issue.

* 15712: Add shortcut info.

* 15712: Fix linter.
This commit is contained in:
Andreas 2025-03-12 02:18:14 +11:00 committed by GitHub
parent c80afea468
commit b8acae2f21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 198 additions and 6 deletions

View file

@ -40,6 +40,7 @@
thumbnailWidth?: number | undefined;
thumbnailHeight?: number | undefined;
selected?: boolean;
focussed?: boolean;
selectionCandidate?: boolean;
disabled?: boolean;
readonly?: boolean;
@ -60,7 +61,9 @@
onRetrieveElement?: ((elment: HTMLElement) => void) | undefined;
onSelect?: ((asset: AssetResponseDto) => void) | undefined;
onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined;
handleFocus?: (() => void) | undefined;
class?: string;
overrideDisplayForTest?: boolean;
}
let {
@ -72,6 +75,7 @@
thumbnailWidth = undefined,
thumbnailHeight = undefined,
selected = false,
focussed = false,
selectionCandidate = false,
disabled = false,
readonly = false,
@ -85,7 +89,9 @@
onRetrieveElement = undefined,
onSelect = undefined,
onMouseEvent = undefined,
handleFocus = undefined,
class: className = '',
overrideDisplayForTest = false,
}: Props = $props();
let {
@ -94,6 +100,7 @@
const componentId = generateId();
let element: HTMLElement | undefined = $state();
let focussableElement: HTMLElement | undefined = $state();
let mouseOver = $state(false);
let intersecting = $state(false);
let lastRetrievedElement: HTMLElement | undefined = $state();
@ -111,6 +118,12 @@
}
});
$effect(() => {
if (focussed && document.activeElement !== focussableElement) {
focussableElement?.focus();
}
});
let width = $derived(thumbnailSize || thumbnailWidth || 235);
let height = $derived(thumbnailSize || thumbnailHeight || 235);
let display = $derived(intersecting);
@ -217,7 +230,7 @@
></canvas>
{/if}
{#if display}
{#if display || overrideDisplayForTest}
<!-- svelte queries for all links on afterNavigate, leading to performance problems in asset-grid which updates
the navigation url on scroll. Replace this with button for now. -->
<div
@ -226,14 +239,20 @@
class:cursor-pointer={!disabled}
onmouseenter={onMouseEnter}
onmouseleave={onMouseLeave}
onkeypress={(evt) => {
onkeydown={(evt) => {
if (evt.key === 'Enter') {
callClickHandlers();
}
if (evt.key === 'x') {
onSelect?.(asset);
}
}}
tabindex={0}
onclick={handleClick}
role="link"
bind:this={focussableElement}
onfocus={handleFocus}
data-testid="container-with-tabindex"
>
{#if mouseOver && !disableMouseOver}
<!-- lazy show the url on mouse over-->
@ -244,7 +263,7 @@
style:height="{height}px"
href={currentUrlReplaceAssetId(asset.id)}
onclick={(evt) => evt.preventDefault()}
tabindex={0}
tabindex={-1}
aria-label="Thumbnail URL"
>
</a>
@ -258,6 +277,8 @@
class="absolute p-2 focus:outline-none"
class:cursor-not-allowed={disabled}
role="checkbox"
tabindex={-1}
onfocus={handleFocus}
aria-checked={selected}
{disabled}
>