mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Menus and the various controls that use menus can now be controlled with the keyboard.
This commit is contained in:
parent
a933c5e4c1
commit
fcf08e6f76
@ -41,6 +41,25 @@ export class ComboBox<T> extends LabelledControl {
|
||||
this.input_element.onmousedown = () => {
|
||||
menu_visible.val = true;
|
||||
};
|
||||
this.input_element.onkeydown = (e: Event) => {
|
||||
const key = (e as KeyboardEvent).key;
|
||||
|
||||
switch (key) {
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
this.menu.hover_next();
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
this.menu.hover_prev();
|
||||
break;
|
||||
|
||||
case "Enter":
|
||||
this.menu.select_hovered();
|
||||
break;
|
||||
}
|
||||
};
|
||||
this.input_element.onblur = () => {
|
||||
menu_visible.val = false;
|
||||
};
|
||||
|
@ -69,7 +69,9 @@ export class DropDown<T> extends Control {
|
||||
}
|
||||
|
||||
private button_mouseup(): void {
|
||||
if (!this.just_opened) {
|
||||
if (this.just_opened) {
|
||||
this.menu.focus();
|
||||
} else {
|
||||
this.menu.visible.val = false;
|
||||
}
|
||||
|
||||
|
@ -2,24 +2,25 @@
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
border: var(--control-border);
|
||||
--scrollbar-color: hsl(0, 0%, 18%);
|
||||
--scrollbar-thumb-color: hsl(0, 0%, 22%);
|
||||
}
|
||||
|
||||
.core_Menu .core_Menu_inner {
|
||||
.core_Menu > .core_Menu_inner {
|
||||
overflow: auto;
|
||||
background-color: var(--control-bg-color);
|
||||
max-height: 500px;
|
||||
border: var(--control-inner-border);
|
||||
}
|
||||
|
||||
.core_Menu .core_Menu_inner > * {
|
||||
.core_Menu > .core_Menu_inner > * {
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.core_Menu .core_Menu_inner > *:hover {
|
||||
.core_Menu > .core_Menu_inner > .core_Menu_hovered {
|
||||
background-color: var(--control-bg-color-hover);
|
||||
color: var(--control-text-color-hover);
|
||||
}
|
||||
|
@ -11,22 +11,26 @@ export class Menu<T> extends Widget {
|
||||
|
||||
private readonly to_label: (element: T) => string;
|
||||
private readonly items: Property<T[]>;
|
||||
private readonly inner_element = el.div({ class: "core_Menu_inner" });
|
||||
private readonly related_element: HTMLElement;
|
||||
private readonly _selected: WidgetProperty<T | undefined>;
|
||||
private hovered_index?: number;
|
||||
private hovered_element?: HTMLElement;
|
||||
|
||||
constructor(
|
||||
items: T[] | Property<T[]>,
|
||||
to_label: (element: T) => string,
|
||||
related_element: HTMLElement,
|
||||
) {
|
||||
super(el.div({ class: "core_Menu" }));
|
||||
super(el.div({ class: "core_Menu", tab_index: -1 }));
|
||||
|
||||
this.visible.val = false;
|
||||
|
||||
this.element.onmouseup = (e: Event) => this.mouseup(e);
|
||||
this.element.onmouseup = this.mouseup;
|
||||
this.element.onkeydown = this.keydown;
|
||||
|
||||
const inner_element = el.div({ class: "core_Menu_inner" });
|
||||
this.element.append(inner_element);
|
||||
this.inner_element.onmouseover = this.inner_mouseover;
|
||||
this.element.append(this.inner_element);
|
||||
|
||||
this.to_label = to_label;
|
||||
this.items = Array.isArray(items) ? property(items) : items;
|
||||
@ -38,8 +42,8 @@ export class Menu<T> extends Widget {
|
||||
this.disposables(
|
||||
this.items.observe(
|
||||
({ value: items }) => {
|
||||
inner_element.innerHTML = "";
|
||||
inner_element.append(
|
||||
this.inner_element.innerHTML = "";
|
||||
this.inner_element.append(
|
||||
...items.map((item, index) =>
|
||||
el.div({ text: to_label(item), data: { index: index.toString() } }),
|
||||
),
|
||||
@ -56,22 +60,68 @@ export class Menu<T> extends Widget {
|
||||
);
|
||||
}
|
||||
|
||||
hover_next(): void {
|
||||
this.hover_item(
|
||||
this.hovered_index != undefined ? (this.hovered_index + 1) % this.items.val.length : 0,
|
||||
);
|
||||
}
|
||||
|
||||
hover_prev(): void {
|
||||
this.hover_item(this.hovered_index ? this.hovered_index - 1 : this.items.val.length - 1);
|
||||
}
|
||||
|
||||
select_hovered(): void {
|
||||
if (this.hovered_index != undefined) {
|
||||
this.select_item(this.hovered_index);
|
||||
}
|
||||
}
|
||||
|
||||
protected set_visible(visible: boolean): void {
|
||||
super.set_visible(visible);
|
||||
this.hover_item();
|
||||
this.inner_element.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
protected set_selected(): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
private mouseup(e: Event): void {
|
||||
private mouseup = (e: Event): void => {
|
||||
if (!(e.target instanceof HTMLElement)) return;
|
||||
|
||||
const index_str = e.target.dataset.index;
|
||||
if (index_str == undefined) return;
|
||||
|
||||
const element = this.items.val[parseInt(index_str, 10)];
|
||||
if (!element) return;
|
||||
this.select_item(parseInt(index_str, 10));
|
||||
};
|
||||
|
||||
this.selected.set_val(element, { silent: false });
|
||||
this.visible.set_val(false, { silent: false });
|
||||
}
|
||||
private keydown = (e: Event): void => {
|
||||
const key = (e as KeyboardEvent).key;
|
||||
|
||||
switch (key) {
|
||||
case "ArrowDown":
|
||||
this.hover_next();
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
this.hover_prev();
|
||||
break;
|
||||
|
||||
case "Enter":
|
||||
this.select_hovered();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
private inner_mouseover = (e: Event): void => {
|
||||
if (e.target && e.target instanceof HTMLElement) {
|
||||
const index = e.target.dataset.index;
|
||||
|
||||
if (index != undefined) {
|
||||
this.hover_item(parseInt(index, 10));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private document_mousedown = (e: Event): void => {
|
||||
if (
|
||||
@ -88,4 +138,31 @@ export class Menu<T> extends Widget {
|
||||
this.visible.set_val(false, { silent: false });
|
||||
}
|
||||
};
|
||||
|
||||
private hover_item(index?: number): void {
|
||||
if (this.hovered_element) {
|
||||
this.hovered_element.classList.remove("core_Menu_hovered");
|
||||
}
|
||||
|
||||
if (index == undefined) {
|
||||
this.hovered_index = undefined;
|
||||
this.hovered_element = undefined;
|
||||
} else {
|
||||
this.hovered_element = this.inner_element.children.item(index) as HTMLElement;
|
||||
|
||||
if (this.hovered_element) {
|
||||
this.hovered_index = index;
|
||||
this.hovered_element.classList.add("core_Menu_hovered");
|
||||
this.hovered_element.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private select_item(index: number): void {
|
||||
const item = this.items.val[index];
|
||||
if (!item) return;
|
||||
|
||||
this.selected.set_val(item, { silent: false });
|
||||
this.visible.set_val(false, { silent: false });
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,9 @@ export class Select<T> extends LabelledControl {
|
||||
}
|
||||
|
||||
private button_mouseup(): void {
|
||||
if (!this.just_opened) {
|
||||
if (this.just_opened) {
|
||||
this.menu.focus();
|
||||
} else {
|
||||
this.menu.visible.val = false;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user