From 2dbfbcba2b9b055c9c2fba3bffc01d2d0fa2990b Mon Sep 17 00:00:00 2001 From: Christoph Fricke Date: Wed, 17 Aug 2022 15:29:37 +0200 Subject: [PATCH] feat(item): dispatch events for focus changes refs: #4 --- src/components/item.ts | 9 ++++- .../item-interactions-controller.ts | 27 +++++++++++++- src/core/events.ts | 37 +++++++++++++++++++ src/main.ts | 7 +++- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/components/item.ts b/src/components/item.ts index a101e44..169fa89 100644 --- a/src/components/item.ts +++ b/src/components/item.ts @@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js"; import { cameraProp } from "../controllers/camera-controller.js"; import { ItemInteractionsController } from "../controllers/item-interactions-controller.js"; import { MuttiDate } from "../core/date.js"; -import { ActionEvent } from "../core/events.js"; +import { ItemFocusEvent } from "../core/events.js"; import { varX } from "../core/properties.js"; /** Custom CSS property names that are related to items. */ @@ -53,6 +53,13 @@ export class MuttiItemElement extends LitElement { ); @property({ type: Number, attribute: false }) subTrack = 1; + override focus(): void { + const shouldContinue = this.dispatchEvent( + new ItemFocusEvent(this.start, this.end) + ); + if (shouldContinue) super.focus({ preventScroll: true }); + } + protected override willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("subTrack")) { this.style.setProperty(itemProp.subTrack, this.subTrack.toString()); diff --git a/src/controllers/item-interactions-controller.ts b/src/controllers/item-interactions-controller.ts index 68a6e61..d08ca96 100644 --- a/src/controllers/item-interactions-controller.ts +++ b/src/controllers/item-interactions-controller.ts @@ -1,5 +1,10 @@ import type { LitElement, ReactiveController } from "lit"; -import { ActionEvent, DeleteEvent } from "../core/events.js"; +import { + ActionEvent, + DeleteEvent, + FocusChangeEvent, + FocusChangeLocation, +} from "../core/events.js"; export class ItemInteractionsController implements ReactiveController { constructor(private readonly host: LitElement) { @@ -24,6 +29,22 @@ export class ItemInteractionsController implements ReactiveController { case "Delete": if (!e.repeat) this.delete(); break; + case "ArrowLeft": + e.preventDefault(); + this.changeFocus("left"); + break; + case "ArrowRight": + e.preventDefault(); + this.changeFocus("right"); + break; + case "ArrowUp": + e.preventDefault(); + this.changeFocus("up"); + break; + case "ArrowDown": + e.preventDefault(); + this.changeFocus("down"); + break; } }; @@ -31,6 +52,10 @@ export class ItemInteractionsController implements ReactiveController { this.action(); }; + private changeFocus(where: FocusChangeLocation) { + this.host.dispatchEvent(new FocusChangeEvent(where)); + } + private delete() { this.host.dispatchEvent(new DeleteEvent()); } diff --git a/src/core/events.ts b/src/core/events.ts index 9fa048d..5d31ace 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -1,3 +1,5 @@ +import type { MuttiDate } from "./date.js"; + abstract class MuttiEvent extends Event { static match(_: Event): _ is MuttiEvent { throw new TypeError( @@ -10,6 +12,8 @@ declare global { interface GlobalEventHandlersEventMap { [ActionEvent.type]: ActionEvent; [DeleteEvent.type]: DeleteEvent; + [FocusChangeEvent.type]: FocusChangeEvent; + [ItemFocusEvent.type]: ItemFocusEvent; } } @@ -36,3 +40,36 @@ export class DeleteEvent extends MuttiEvent { return e instanceof DeleteEvent; } } + +export type FocusChangeLocation = "left" | "right" | "up" | "down"; +export class FocusChangeEvent extends MuttiEvent { + static type = "focuschange" as const; + + constructor(public readonly where: FocusChangeLocation) { + super(FocusChangeEvent.type, { bubbles: true }); + } + + static override match(e: Event): e is FocusChangeEvent { + return e instanceof FocusChangeEvent; + } +} + +/** + * This event is dispatched before an item is focused via the keyboard. + * If the event is cancelled, the item will not be focussed and will + * not be moved into view. + */ +export class ItemFocusEvent extends MuttiEvent { + static type = "itemfocus" as const; + + constructor( + public readonly start: MuttiDate, + public readonly end: MuttiDate + ) { + super(ItemFocusEvent.type, { bubbles: true, cancelable: true }); + } + + static override match(e: Event): e is ItemFocusEvent { + return e instanceof ItemFocusEvent; + } +} diff --git a/src/main.ts b/src/main.ts index d446236..453b1fe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,4 +3,9 @@ export { MuttiLabelElement } from "./components/label.js"; export { MuttiTimelineElement } from "./components/timeline.js"; export { MuttiTrackElement } from "./components/track.js"; -export { ActionEvent, DeleteEvent } from "./core/events.js"; +export { + ActionEvent, + DeleteEvent, + FocusChangeEvent, + ItemFocusEvent, +} from "./core/events.js";