diff --git a/src/App.svelte b/src/App.svelte index 47e379b..64024c1 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,27 +1,15 @@ @@ -29,16 +17,17 @@ -
+
- +
about path
diff --git a/src/lib/actions/focusAction.ts b/src/lib/actions/focusAction.ts index ad9729c..e23548e 100644 --- a/src/lib/actions/focusAction.ts +++ b/src/lib/actions/focusAction.ts @@ -1,19 +1,25 @@ export type Registerer = (htmlElement: HTMLElement) => { destroy: () => void }; +export type Direction = 'up' | 'down' | 'left' | 'right'; +export type FlowDirection = 'vertical' | 'horizontal'; + export class Container { id: symbol; name: string; - parent?: Container; - children: Container[] = []; - htmlElement?: HTMLElement; - private upNeighbor?: Container; - private downNeighbor?: Container; - private leftNeighbor?: Container; - private rightNeighbor?: Container; + private parent?: Container; + private children: Container[] = []; + private htmlElement?: HTMLElement; + private neighbors: Record = { + up: undefined, + down: undefined, + left: undefined, + right: undefined + }; + private focusByDefault: boolean = false; - direction: 'horizontal' | 'vertical' = 'horizontal'; + private direction: FlowDirection = 'vertical'; - focusIndex: number = 0; + private focusIndex: number = 0; static focusedObject: Container; static objects = new Map(); @@ -22,191 +28,150 @@ export class Container { this.id = Symbol(); this.name = name; Container.objects.set(this.id, this); + } - if (!Container.focusedObject) { + setDirection(direction: FlowDirection) { + this.direction = direction; + return this; + } + + setFocusByDefault(focusByDefault: boolean) { + this.focusByDefault = focusByDefault; + return this; + } + + createChild(name: string = '') { + const child = new Container(name); + this.addChild(child); + return child; + } + + focus() { + if (this.children.length > 0) { + this.children[this.focusIndex]?.focus(); + } else if (this.htmlElement) { + this.htmlElement.focus({ preventScroll: true }); + this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); Container.focusedObject = this; + this.updateFocusIndex(); + } + } + + updateFocusIndex(container?: Container) { + if (container) { + const index = this.children.indexOf(container); + this.focusIndex = index === -1 ? this.focusIndex : index; + } + if (this.parent) { + this.parent.updateFocusIndex(this); + } + } + + isFocusable() { + if (this.htmlElement) { + return true; + } else { + for (const child of this.children) { + if (child.isFocusable()) { + return true; + } + } + } + } + + getFocusableNeighbor(direction: Direction): Container | undefined { + const canLoop = + (this.direction === 'vertical' && + ((direction === 'up' && this.focusIndex !== 0) || + (direction === 'down' && this.focusIndex !== this.children.length - 1))) || + (this.direction === 'horizontal' && + ((direction === 'left' && this.focusIndex !== 0) || + (direction === 'right' && this.focusIndex !== this.children.length - 1))); + if (this.children.length > 0 && canLoop) { + if (direction === 'up' || direction === 'left') { + let index = this.focusIndex - 1; + while (index >= 0) { + if (this.children[index].isFocusable()) { + return this.children[index]; + } + index--; + } + } else if (direction === 'down' || direction === 'right') { + let index = this.focusIndex + 1; + while (index < this.children.length) { + if (this.children[index].isFocusable()) { + return this.children[index]; + } + index++; + } + } + } else if (this.neighbors[direction]?.isFocusable()) { + return this.neighbors[direction]; + } else { + return this.parent?.getFocusableNeighbor(direction); + } + } + + giveFocus(direction: Direction) { + const neighbor = this.getFocusableNeighbor(direction); + console.log('Giving focus to', direction, 'neighbor: ', neighbor?.name, neighbor); + if (neighbor) { + neighbor.focus(); + return true; + } else { + return false; } } getRegisterer(): Registerer { return (htmlElement: HTMLElement) => { - this.addChildElement(htmlElement); + if (this.htmlElement) console.warn('Registering to a container that has an element.'); + + this.createChild().addHtmlElement(htmlElement); + + if (!Container.focusedObject && this.shouldFocusByDefault()) { + this.focus(); + } return { destroy: () => { - this.removeChildElement(htmlElement); + this.removeHtmlElement(); } }; }; } - giveFocus(direction: 'up' | 'down' | 'left' | 'right') { - console.log('This: '); - this.log(); - - console.log('Giving focus to', direction, 'neighbor'); - const upNeighbor = this.getUpNeighbor(); - const downNeighbor = this.getDownNeighbor(); - const leftNeighbor = this.getLeftNeighbor(); - const rightNeighbor = this.getRightNeighbor(); - if (direction === 'up' && upNeighbor) { - if (upNeighbor.direction === 'vertical') { - upNeighbor.focusIndex = upNeighbor.children.length - 1; - } - - upNeighbor.focusElement(); - } else if (direction === 'down' && downNeighbor) { - if (downNeighbor.direction === 'vertical') { - downNeighbor.focusIndex = 0; - } - - downNeighbor.focusElement(); - } else if (direction === 'left' && leftNeighbor) { - if (leftNeighbor.direction === 'horizontal') { - leftNeighbor.focusIndex = leftNeighbor.children.length - 1; - } - - leftNeighbor.focusElement(); - } else if (direction === 'right' && rightNeighbor) { - if (rightNeighbor.direction === 'horizontal') { - rightNeighbor.focusIndex = 0; - } - - rightNeighbor.focusElement(); - } else { - return false; - } - - return true; - } - - private getUpNeighbor(): Container | undefined { - return this.upNeighbor || this.parent?.getUpNeighbor(); - } - - private getDownNeighbor(): Container | undefined { - return this.downNeighbor || this.parent?.getDownNeighbor(); - } - - private getLeftNeighbor(): Container | undefined { - return this.leftNeighbor || this.parent?.getLeftNeighbor(); - } - - private getRightNeighbor(): Container | undefined { - return this.rightNeighbor || this.parent?.getRightNeighbor(); - } - - focusElement() { - Container.focusedObject = this; - if (this.htmlElement) { - this.htmlElement.focus({ preventScroll: true }); - this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); - - if (this.parent) { - this.parent.focusIndex = this.parent.children.findIndex((child) => child === this); - } - } else { - this.children[this.focusIndex].focusElement(); - } - } - - private setParent(parent?: Container) { - this.parent = parent; + private addChild(child: Container) { + this.children.push(child); + child.parent = this; + this.htmlElement = undefined; return this; } - private setHtmlElement(htmlElement: HTMLElement) { + private removeChild(child: Container) { + this.children = this.children.filter((c) => c !== child); + child.parent = undefined; + return this; + } + + private addHtmlElement(htmlElement: HTMLElement) { + if (this.children.length > 0) { + console.warn('Adding an html element to a container that has children.'); + for (const child of this.children) { + this.removeChild(child); + } + } this.htmlElement = htmlElement; return this; } - addChild(child: Container) { - console.log('Adding child', child.name, 'to', this.name); - if (this.direction === 'vertical' && this.children.length >= 1) { - child.setUpNeighbor(this.children[this.children.length - 1]); - } else if (this.direction === 'horizontal' && this.children.length >= 1) { - child.setLeftNeighbor(this.children[this.children.length - 1]); - } - this.children.push(child.setParent(this)); - console.log('After add', this); + private removeHtmlElement() { + this.htmlElement = undefined; return this; } - private addChildElement(htmlElement: HTMLElement) { - const childContainer = new Container().setHtmlElement(htmlElement).setParent(this); - - if (this.direction === 'vertical' && this.children.length >= 1) { - childContainer.setUpNeighbor(this.children[this.children.length - 1]); - } else if (this.direction === 'horizontal' && this.children.length >= 1) { - childContainer.setLeftNeighbor(this.children[this.children.length - 1]); - } - - this.children.push(childContainer); - return this; - } - - private removeChildElement(htmlElement: HTMLElement) { - const child = this.children.find((child) => child.htmlElement === htmlElement); - child?.setParent(undefined)?.removeNeighbors(); - this.children = this.children.filter((child) => child.htmlElement !== htmlElement); - return this; - } - - private removeNeighbors() { - if (this.upNeighbor?.downNeighbor === this) { - this.upNeighbor.downNeighbor = undefined; - } - if (this.downNeighbor?.upNeighbor === this) { - this.downNeighbor.upNeighbor = undefined; - } - if (this.leftNeighbor?.rightNeighbor === this) { - this.leftNeighbor.rightNeighbor = undefined; - } - if (this.rightNeighbor?.leftNeighbor === this) { - this.rightNeighbor.leftNeighbor = undefined; - } - this.upNeighbor = undefined; - this.downNeighbor = undefined; - this.leftNeighbor = undefined; - this.rightNeighbor = undefined; - return this; - } - - setUpNeighbor(upNeighbor: Container) { - this.upNeighbor = upNeighbor; - upNeighbor.downNeighbor = this; - return this; - } - - setDownNeighbor(downNeighbor: Container) { - this.downNeighbor = downNeighbor; - downNeighbor.upNeighbor = this; - return this; - } - - setLeftNeighbor(leftNeighbor: Container) { - this.leftNeighbor = leftNeighbor; - leftNeighbor.rightNeighbor = this; - return this; - } - - setRightNeighbor(rightNeighbor: Container) { - this.rightNeighbor = rightNeighbor; - rightNeighbor.leftNeighbor = this; - return this; - } - - setDirection(direction: 'horizontal' | 'vertical') { - this.direction = direction; - return this; - } - - log() { - console.log(this.name, this); - if (this.parent) { - console.log('With parent: '); - this.parent.log(); - } + private shouldFocusByDefault(): boolean { + return this.focusByDefault || this.parent?.shouldFocusByDefault() || false; } } @@ -218,6 +183,8 @@ export function handleKeyboardNavigation(event: KeyboardEvent) { return; } + console.log('Currently focused object: ', currentlyFocusedObject.name, currentlyFocusedObject); + if (event.key === 'ArrowUp') { if (currentlyFocusedObject.giveFocus('up')) event.preventDefault(); } else if (event.key === 'ArrowDown') { @@ -228,16 +195,3 @@ export function handleKeyboardNavigation(event: KeyboardEvent) { if (currentlyFocusedObject.giveFocus('right')) event.preventDefault(); } } - -const navBar = new Container('navBar').setDirection('vertical'); -const main = new Container('main').setDirection('vertical'); -const home = new Container('home').setDirection('vertical'); - -main.setLeftNeighbor(navBar); -main.addChild(home); - -export const navigationContainers = { - home, - main, - navBar -}; diff --git a/src/lib/components/Carousel/CarouselPlaceholderItems.svelte b/src/lib/components/Carousel/CarouselPlaceholderItems.svelte index 047e7a8..43e1dc5 100644 --- a/src/lib/components/Carousel/CarouselPlaceholderItems.svelte +++ b/src/lib/components/Carousel/CarouselPlaceholderItems.svelte @@ -5,7 +5,7 @@ export let orientation: 'landscape' | 'portrait' = 'landscape'; export let container: Container; - let registerer = container.getRegisterer(); + let registerer = container.createChild('carousel').setDirection('horizontal').getRegisterer(); {#each Array(10) as _, i (i)} diff --git a/src/lib/components/Selectable.svelte b/src/lib/components/Selectable.svelte index 8063609..50cac16 100644 --- a/src/lib/components/Selectable.svelte +++ b/src/lib/components/Selectable.svelte @@ -1,3 +1,7 @@ + +
diff --git a/src/lib/pages/HomePage.svelte b/src/lib/pages/HomePage.svelte index 10faf1c..e82141c 100644 --- a/src/lib/pages/HomePage.svelte +++ b/src/lib/pages/HomePage.svelte @@ -1,20 +1,16 @@
- + - +