From 423bcbae3463777fb135ba1779b503cc804fd551 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Sun, 10 May 2026 00:04:23 +0200 Subject: [PATCH] Extract selected item scrolling directive --- src/client/src/components/ActionPalette.ts | 15 +++------ src/client/src/components/AutocompleteMenu.ts | 13 ++------ src/client/src/components/CommandPicker.ts | 13 ++------ .../src/components/scrollWhenSelected.ts | 31 +++++++++++++++++++ 4 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 src/client/src/components/scrollWhenSelected.ts diff --git a/src/client/src/components/ActionPalette.ts b/src/client/src/components/ActionPalette.ts index 72c6627..623f8b2 100644 --- a/src/client/src/components/ActionPalette.ts +++ b/src/client/src/components/ActionPalette.ts @@ -2,6 +2,7 @@ import { LitElement, html, type PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import type { AppAction } from "../actions"; import { formatShortcut } from "../keyboardShortcuts"; +import { scrollWhenSelected } from "./scrollWhenSelected"; import { actionPaletteStyles } from "./shared"; @customElement("action-palette") @@ -33,7 +34,7 @@ export class ActionPalette extends LitElement {
${actions.length === 0 ? html`
No actions found.
` : actions.map((action, index) => html` - @@ -36,14 +37,6 @@ export class CommandPicker extends LitElement { this.renderRoot.querySelector(".options")?.focus(); } - protected override updated(changed: PropertyValues) { - if (changed.has("selectedIndex") || changed.has("options")) this.scrollSelectedIntoView(); - } - - private scrollSelectedIntoView() { - this.renderRoot.querySelector(".options button.selected")?.scrollIntoView({ block: "nearest" }); - } - private handleKeyDown(event: KeyboardEvent) { if (event.key === "Escape") { event.preventDefault(); diff --git a/src/client/src/components/scrollWhenSelected.ts b/src/client/src/components/scrollWhenSelected.ts new file mode 100644 index 0000000..6f9bc22 --- /dev/null +++ b/src/client/src/components/scrollWhenSelected.ts @@ -0,0 +1,31 @@ +import { Directive, PartType, directive, type Part, type PartInfo } from "lit/directive.js"; + +class ScrollWhenSelectedDirective extends Directive { + private readonly isElementPart: boolean; + private wasSelected = false; + private previousKey: unknown; + + constructor(partInfo: PartInfo) { + super(partInfo); + this.isElementPart = partInfo.type === PartType.ELEMENT; + } + + override update(part: Part, [selected, key]: [boolean, unknown?]) { + if (!this.isElementPart) throw new Error("scrollWhenSelected must be used on an element"); + if (selected && (!this.wasSelected || key !== this.previousKey)) { + const element = (part as Part & { element?: Element }).element; + requestAnimationFrame(() => { + if (element instanceof HTMLElement) element.scrollIntoView({ block: "nearest" }); + }); + } + this.wasSelected = selected; + this.previousKey = key; + return undefined; + } + + override render(_selected: boolean, _key?: unknown) { + return undefined; + } +} + +export const scrollWhenSelected = directive(ScrollWhenSelectedDirective);