feat: Improve container on:navigate

This commit is contained in:
Aleksi Lassila
2024-04-11 19:20:40 +03:00
parent 428258798c
commit 2f0f048d89
7 changed files with 68 additions and 26 deletions

View File

@@ -45,6 +45,18 @@
dispatch('enter', { selectable, options, stopPropagation });
})
.setOnNavigate((selectable, options) => {
function preventNavigation() {
options.preventNavigation = true;
}
dispatch('navigate', {
selectable,
options,
preventNavigation,
direction: options.direction
});
})
.setOnSelect(() => {
dispatch('select');
dispatch('clickOrSelect');

View File

@@ -3,10 +3,10 @@
</script>
<Container
handleNavigateOut={{
left: () => {
on:navigate={({ detail }) => {
if (detail.direction === 'left' && detail.options.willLeaveContainer) {
history.back();
return false;
detail.preventNavigation();
}
}}
focusOnMount

View File

@@ -42,10 +42,15 @@
<Container class="flex-1 flex">
<HeroShowcaseBackground {urls} {index} />
<Container
handleNavigateOut={{
right: onNext,
left: onPrevious,
up: () => Selectable.giveFocus('left', true) || true
on:navigate={({ detail }) => {
if (detail.options.direction === 'right') {
if (onNext()) detail.preventNavigation();
} else if (detail.options.direction === 'left') {
if (onPrevious()) detail.preventNavigation();
} else if (detail.options.direction === 'up') {
Selectable.giveFocus('left', true);
detail.preventNavigation();
}
}}
/>
<div class="flex flex-1 z-10">

View File

@@ -8,10 +8,10 @@
</script>
<Container
handleNavigateOut={{
left: () => {
on:navigate={({ detail }) => {
if (detail.direction === 'left' && detail.options.willLeaveContainer) {
modalStack.close(modalId);
return true;
detail.preventNavigation();
}
}}
focusOnMount

View File

@@ -54,8 +54,10 @@
<Container
class="h-screen flex flex-col py-12 px-20 relative"
on:enter={scrollIntoView({ top: 0 })}
handleNavigateOut={{
down: () => episodesSelectable?.focusChild(1)
on:navigate={({ detail }) => {
if (detail.direction === 'down') {
if (episodesSelectable?.focusChild(1)) detail.preventNavigation();
}
}}
>
<HeroCarousel

View File

@@ -92,7 +92,6 @@
</script>
<Container
handleNavigateOut={{}}
focusOnMount
trapFocus
class={classNames('fixed inset-0 bg-black overflow-auto', {

View File

@@ -27,20 +27,32 @@ export type EnterEvent = {
stopPropagation: () => void;
};
type NavigateEventOptions = {};
export type NavigateEvent = {
selectable: Selectable;
options: NavigateEventOptions;
preventDefault: () => void;
};
const createFocusHandlerOptions = (): FocusEventOptions => ({
setFocusedElement: true,
propagate: true
});
type NavigateEventOptions = {
willLeaveContainer: boolean;
preventNavigation: boolean;
direction: Direction;
};
export type NavigateEvent = {
selectable: Selectable;
direction: Direction;
options: NavigateEventOptions;
preventNavigation: () => void;
};
const createNavigateHandlerOptions = (direction: Direction): NavigateEventOptions => ({
willLeaveContainer: false,
preventNavigation: false,
direction
});
export type FocusHandler = (selectable: Selectable, options: FocusEventOptions) => void;
export type NavigationHandler = (selectable: Selectable, options: NavigateEventOptions) => void;
export class Selectable {
id: symbol;
@@ -57,6 +69,7 @@ export class Selectable {
private canFocusEmpty: boolean = true;
private trapFocus: boolean = false;
private navigationActions: NavigationActions = {};
private onNavigate: NavigationHandler = () => {};
private isActive: boolean = true;
private onFocus: FocusHandler = () => {};
private onSelect?: () => void;
@@ -196,6 +209,7 @@ export class Selectable {
private giveFocus(direction: Direction, bypassActions: boolean = false): boolean {
const focusIndex = get(this.focusIndex);
const navigationEventOptions = createNavigateHandlerOptions(direction);
const indexAddition = {
up: this.direction === 'vertical' ? -1 : -this.gridColumns,
@@ -220,6 +234,8 @@ export class Selectable {
while (index >= 0 && index < this.children.length) {
const children = this.children[index];
if (children && children.isFocusable()) {
this.onNavigate(this, navigationEventOptions);
if (navigationEventOptions.preventNavigation) return true;
children.focus();
return true;
}
@@ -228,11 +244,12 @@ export class Selectable {
}
// About to leave this container (=coulnd't cycle siblings)
const action = this.navigationActions[direction];
if (action && !bypassActions && action(this)) {
return true;
} else if (this.neighbors[direction]?.isFocusable()) {
navigationEventOptions.willLeaveContainer = true;
if (!bypassActions) {
this.onNavigate(this, navigationEventOptions);
if (navigationEventOptions.preventNavigation) return true;
}
if (this.neighbors[direction]?.isFocusable()) {
this.neighbors[direction]?.focus();
return true;
} else if (!this.trapFocus) {
@@ -572,6 +589,11 @@ export class Selectable {
this.onSelect = onSelect;
return this;
}
setOnNavigate(onNavigate: NavigationHandler) {
this.onNavigate = onNavigate;
return this;
}
}
export function handleKeyboardNavigation(event: KeyboardEvent) {
@@ -603,6 +625,8 @@ export function handleKeyboardNavigation(event: KeyboardEvent) {
else {
currentlyFocusedObject.select();
}
} else if (event.key === 'Back') {
} else if (event.key === 'MediaPlayPause') {
}
}