From 652894fcc907870ac473055d49e0b9fa08f10693 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Sat, 23 Mar 2024 22:53:33 +0200 Subject: [PATCH] feat: Add active property to navigation elements, code cleanup --- src/Container.svelte | 6 +- src/lib/components-old/PersonPage.svelte | 2 +- .../Poster.svelte => Card/Card.svelte} | 3 +- .../HeroShowcase/HeroShowcase.svelte | 14 ++-- .../components/HeroShowcase/PageDots.svelte | 4 +- src/lib/pages/BrowseSeriesPage.svelte | 2 +- src/lib/selectable.ts | 75 ++++++++++++++----- 7 files changed, 75 insertions(+), 31 deletions(-) rename src/lib/components/{Poster/Poster.svelte => Card/Card.svelte} (98%) diff --git a/src/Container.svelte b/src/Container.svelte index 6d79893..7771350 100644 --- a/src/Container.svelte +++ b/src/Container.svelte @@ -8,6 +8,8 @@ export let focusOnMount = false; export let debugOutline = false; + export let active = true; + export let navigationActions: NavigationActions = {}; const { registerer, ...rest } = new Selectable(name) @@ -21,6 +23,8 @@ export let tag = 'div'; + $: container.setIsActive(active); + onMount(() => { rest.container._initializeSelectable(); @@ -37,7 +41,7 @@ { if (openInModal) { if (tmdbId) { diff --git a/src/lib/components/HeroShowcase/HeroShowcase.svelte b/src/lib/components/HeroShowcase/HeroShowcase.svelte index 6bb8ec0..3ae1e27 100644 --- a/src/lib/components/HeroShowcase/HeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/HeroShowcase.svelte @@ -8,7 +8,7 @@ import { ChevronRight, DotFilled } from 'radix-icons-svelte'; import CardPlaceholder from '../Card/CardPlaceholder.svelte'; import classNames from 'classnames'; - import Poster from '../Poster/Poster.svelte'; + import Card from '../Card/Card.svelte'; import { TMDB_POSTER_SMALL } from '../../constants'; export let items: Promise = Promise.resolve([]); @@ -40,13 +40,13 @@ Selectable.focusLeft() || true }} - > + /> +
{#await items}
@@ -70,7 +70,11 @@ {@const item = items[showcaseIndex]}
- +
{error.message}

{/await}
- +
diff --git a/src/lib/components/HeroShowcase/PageDots.svelte b/src/lib/components/HeroShowcase/PageDots.svelte index 1f355b7..1c2b6ce 100644 --- a/src/lib/components/HeroShowcase/PageDots.svelte +++ b/src/lib/components/HeroShowcase/PageDots.svelte @@ -27,14 +27,14 @@
onJump(i)}>
-
+
{/each} diff --git a/src/lib/pages/BrowseSeriesPage.svelte b/src/lib/pages/BrowseSeriesPage.svelte index a2c124f..f7199f5 100644 --- a/src/lib/pages/BrowseSeriesPage.svelte +++ b/src/lib/pages/BrowseSeriesPage.svelte @@ -5,7 +5,7 @@ import { settings } from '../stores/settings.store'; import type { TitleType } from '../types'; import type { ComponentProps } from 'svelte'; - import Poster from '../components/Poster/Poster.svelte'; + import Poster from '../components/Card/Card.svelte'; import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi'; import { jellyfinItemsStore } from '../stores/data.store'; import Carousel from '../components/Carousel/Carousel.svelte'; diff --git a/src/lib/selectable.ts b/src/lib/selectable.ts index d9cd3d0..e1263ed 100644 --- a/src/lib/selectable.ts +++ b/src/lib/selectable.ts @@ -26,6 +26,7 @@ export class Selectable { private focusByDefault: boolean = false; private isInitialized: boolean = false; private navigationActions: NavigationActions = {}; + private isActive: boolean = true; private direction: FlowDirection = 'vertical'; @@ -69,14 +70,45 @@ export class Selectable { } focus() { + function updateFocusIndex(currentSelectable: Selectable, selectable?: Selectable) { + if (selectable) { + const index = currentSelectable.children.indexOf(selectable); + currentSelectable.focusIndex.update((prev) => (index === -1 ? prev : index)); + } + if (currentSelectable.parent) { + updateFocusIndex(currentSelectable.parent, currentSelectable); + } + } + if (this.children.length > 0) { - this.children[get(this.focusIndex)]?.focus(); + const focusIndex = get(this.focusIndex); + + if (this.children[focusIndex]?.isFocusable()) { + this.children[focusIndex].focus(); + } else { + let i = focusIndex; + while (i < this.children.length) { + if (this.children[i].isFocusable()) { + this.children[i].focus(); + return; + } + i++; + } + i = focusIndex - 1; + while (i >= 0) { + if (this.children[i].isFocusable()) { + this.children[i].focus(); + return; + } + i--; + } + } } else if (this.htmlElement) { this.htmlElement.focus({ preventScroll: true }); // this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); this.scrollIntoView(50); Selectable.focusedObject.set(this); - this.updateFocusIndex(); + updateFocusIndex(this); } } @@ -98,17 +130,12 @@ export class Selectable { } } - updateFocusIndex(selectable?: Selectable) { - if (selectable) { - const index = this.children.indexOf(selectable); - this.focusIndex.update((prev) => (index === -1 ? prev : index)); - } - if (this.parent) { - this.parent.updateFocusIndex(this); - } - } + /** + * @returns {boolean} whether the selectable is focusable + */ + isFocusable(): boolean { + if (!this.isActive) return false; - isFocusable() { if (this.htmlElement) { return this.htmlElement.tabIndex >= 0; } else { @@ -118,20 +145,23 @@ export class Selectable { } } } + + return false; } getFocusableNeighbor(direction: Direction): Selectable | undefined { - const canLoop = + const focusIndex = get(this.focusIndex); + const canCycleSiblings = (this.direction === 'vertical' && - ((direction === 'up' && get(this.focusIndex) !== 0) || - (direction === 'down' && get(this.focusIndex) !== this.children.length - 1))) || + ((direction === 'up' && focusIndex !== 0) || + (direction === 'down' && focusIndex !== this.children.length - 1))) || (this.direction === 'horizontal' && - ((direction === 'left' && get(this.focusIndex) !== 0) || - (direction === 'right' && get(this.focusIndex) !== this.children.length - 1))); + ((direction === 'left' && focusIndex !== 0) || + (direction === 'right' && focusIndex !== this.children.length - 1))); - if (this.children.length > 0 && canLoop) { + if (this.children.length > 0 && canCycleSiblings) { if (direction === 'up' || direction === 'left') { - let index = get(this.focusIndex) - 1; + let index = focusIndex - 1; while (index >= 0) { if (this.children[index].isFocusable()) { return this.children[index]; @@ -139,7 +169,7 @@ export class Selectable { index--; } } else if (direction === 'down' || direction === 'right') { - let index = get(this.focusIndex) + 1; + let index = focusIndex + 1; while (index < this.children.length) { if (this.children[index].isFocusable()) { return this.children[index]; @@ -315,6 +345,11 @@ export class Selectable { getNavigationActions(): NavigationActions { return this.navigationActions; } + + setIsActive(isActive: boolean) { + this.isActive = isActive; + return this; + } } export function handleKeyboardNavigation(event: KeyboardEvent) {