Archived
Extract selected item scrolling directive
This commit is contained in:
@@ -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 {
|
||||
</header>
|
||||
<div class="options">
|
||||
${actions.length === 0 ? html`<div class="empty">No actions found.</div>` : actions.map((action, index) => html`
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} @click=${() => { this.run(action); }}>
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} ${scrollWhenSelected(index === this.selectedIndex, action.id)} @click=${() => { this.run(action); }}>
|
||||
<span class="main">
|
||||
<strong>${action.title}</strong>
|
||||
${action.description !== undefined && action.description !== "" ? html`<small>${action.description}</small>` : null}
|
||||
@@ -53,15 +54,9 @@ export class ActionPalette extends LitElement {
|
||||
}
|
||||
|
||||
protected override updated(changed: PropertyValues) {
|
||||
if (changed.has("actions") || changed.has("queryText")) {
|
||||
const maxIndex = Math.max(0, this.filteredActions().length - 1);
|
||||
if (this.selectedIndex > maxIndex) this.selectedIndex = maxIndex;
|
||||
}
|
||||
if (changed.has("selectedIndex") || changed.has("actions") || changed.has("queryText")) this.scrollSelectedIntoView();
|
||||
}
|
||||
|
||||
private scrollSelectedIntoView() {
|
||||
this.renderRoot.querySelector<HTMLElement>(".options button.selected")?.scrollIntoView({ block: "nearest" });
|
||||
if (!changed.has("actions") && !changed.has("queryText")) return;
|
||||
const maxIndex = Math.max(0, this.filteredActions().length - 1);
|
||||
if (this.selectedIndex > maxIndex) this.selectedIndex = maxIndex;
|
||||
}
|
||||
|
||||
private filteredActions(): AppAction[] {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LitElement, html, type PropertyValues } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { scrollWhenSelected } from "./scrollWhenSelected";
|
||||
import { autocompleteStyles, type CompletionItem } from "./shared";
|
||||
|
||||
@customElement("autocomplete-menu")
|
||||
@@ -13,7 +14,7 @@ export class AutocompleteMenu extends LitElement {
|
||||
return html`
|
||||
<div class="menu">
|
||||
${this.items.map((item, index) => html`
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} @mousedown=${(event: MouseEvent) => { event.preventDefault(); this.onPick?.(item); }}>
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} ${scrollWhenSelected(index === this.selectedIndex, item)} @mousedown=${(event: MouseEvent) => { event.preventDefault(); this.onPick?.(item); }}>
|
||||
<strong>${item.insertText}</strong>
|
||||
<span>${item.detail}</span>
|
||||
${item.description !== undefined && item.description !== "" ? html`<small>${item.description}</small>` : null}
|
||||
@@ -23,13 +24,5 @@ export class AutocompleteMenu extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected override updated(changed: PropertyValues) {
|
||||
if (changed.has("selectedIndex") || changed.has("items")) this.scrollSelectedIntoView();
|
||||
}
|
||||
|
||||
private scrollSelectedIntoView() {
|
||||
this.renderRoot.querySelector<HTMLElement>("button.selected")?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
static override styles = autocompleteStyles;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LitElement, html, type PropertyValues } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import type { CommandOption } from "../api";
|
||||
import { scrollWhenSelected } from "./scrollWhenSelected";
|
||||
import { commandPickerStyles } from "./shared";
|
||||
|
||||
@customElement("command-picker")
|
||||
@@ -21,7 +22,7 @@ export class CommandPicker extends LitElement {
|
||||
</header>
|
||||
<div class="options" @keydown=${(event: KeyboardEvent) => { this.handleKeyDown(event); }} tabindex="0">
|
||||
${this.options.map((option, index) => html`
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} @click=${() => this.onPick?.(option.value)}>
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} ${scrollWhenSelected(index === this.selectedIndex, option.value)} @click=${() => this.onPick?.(option.value)}>
|
||||
<span>${option.label}</span>
|
||||
${option.description !== undefined && option.description !== "" ? html`<small>${option.description}</small>` : null}
|
||||
</button>
|
||||
@@ -36,14 +37,6 @@ export class CommandPicker extends LitElement {
|
||||
this.renderRoot.querySelector<HTMLElement>(".options")?.focus();
|
||||
}
|
||||
|
||||
protected override updated(changed: PropertyValues) {
|
||||
if (changed.has("selectedIndex") || changed.has("options")) this.scrollSelectedIntoView();
|
||||
}
|
||||
|
||||
private scrollSelectedIntoView() {
|
||||
this.renderRoot.querySelector<HTMLElement>(".options button.selected")?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user