Made several widgets more keyboard-friendly.

This commit is contained in:
Daan Vanden Bosch 2020-01-01 04:15:03 +01:00
parent 71433d253f
commit 821b894a52
7 changed files with 78 additions and 19 deletions

View File

@ -39,6 +39,10 @@
color: hsl(0, 0%, 75%);
}
.core_Button:focus-within .core_Button_inner {
border: var(--control-inner-border-focus);
}
.core_Button:disabled .core_Button_inner {
background-color: hsl(0, 0%, 15%);
border-color: hsl(0, 0%, 25%);

View File

@ -19,6 +19,8 @@ export class Button extends Control {
private readonly _mousedown: Emitter<MouseEvent>;
private readonly _mouseup: Emitter<MouseEvent>;
private readonly _click: Emitter<MouseEvent>;
private readonly _keydown: Emitter<KeyboardEvent>;
private readonly _keyup: Emitter<KeyboardEvent>;
private readonly _text: WidgetProperty<string>;
private readonly center_element: HTMLSpanElement;
@ -26,6 +28,8 @@ export class Button extends Control {
readonly mousedown: Observable<MouseEvent>;
readonly mouseup: Observable<MouseEvent>;
readonly click: Observable<MouseEvent>;
readonly keydown: Observable<KeyboardEvent>;
readonly keyup: Observable<KeyboardEvent>;
readonly text: WritableProperty<string>;
constructor(options?: ButtonOptions) {
@ -58,6 +62,14 @@ export class Button extends Control {
this.click = this._click;
this.element.onclick = (e: MouseEvent) => this._click.emit({ value: e });
this._keydown = emitter<KeyboardEvent>();
this.keydown = this._keydown;
this.element.onkeydown = (e: KeyboardEvent) => this._keydown.emit({ value: e });
this._keyup = emitter<KeyboardEvent>();
this.keyup = this._keyup;
this.element.onkeyup = (e: KeyboardEvent) => this._keyup.emit({ value: e });
this._text = new WidgetProperty<string>(this, "", this.set_text);
this.text = this._text;

View File

@ -35,7 +35,7 @@ export class DropDown<T> extends Control {
}),
);
this.menu = this.disposable(
new Menu<T>({
new Menu({
items: options.items,
to_label: options.to_label,
related_element: this.element,
@ -49,11 +49,13 @@ export class DropDown<T> extends Control {
this.just_opened = false;
this.disposables(
disposable_listener(this.button.element, "mousedown", () => this.button_mousedown(), {
disposable_listener(this.button.element, "mousedown", this.button_mousedown, {
capture: true,
}),
this.button.mouseup.observe(() => this.button_mouseup()),
this.button.mouseup.observe(this.button_mouseup),
this.button.keydown.observe(this.button_keydown),
this.menu.selected.observe(({ value }) => {
if (value !== undefined) {
@ -71,12 +73,12 @@ export class DropDown<T> extends Control {
this.button.enabled.val = enabled;
}
private button_mousedown(): void {
private button_mousedown = (): void => {
this.just_opened = !this.menu.visible.val;
this.menu.visible.val = true;
}
};
private button_mouseup(): void {
private button_mouseup = (): void => {
if (this.just_opened) {
this.menu.focus();
} else {
@ -84,5 +86,15 @@ export class DropDown<T> extends Control {
}
this.just_opened = false;
};
private button_keydown = ({ value: evt }: { value: KeyboardEvent }): void => {
if (evt.key === "Enter" || evt.key === " ") {
evt.preventDefault();
this.just_opened = !this.menu.visible.val;
this.menu.visible.val = true;
this.menu.focus();
this.menu.hover_next();
}
};
}

View File

@ -23,6 +23,7 @@ export class Menu<T> extends Widget {
private readonly _selected: WidgetProperty<T | undefined>;
private hovered_index?: number;
private hovered_element?: HTMLElement;
private previously_focused_element?: Element;
constructor(options: MenuOptions<T>) {
super();
@ -31,6 +32,7 @@ export class Menu<T> extends Widget {
this.element.onmouseup = this.mouseup;
this.element.onkeydown = this.keydown;
this.element.onblur = this.blur;
this.inner_element.onmouseover = this.inner_mouseover;
this.element.append(this.inner_element);
@ -66,6 +68,11 @@ export class Menu<T> extends Widget {
this.finalize_construction();
}
focus(): void {
this.previously_focused_element = document.activeElement ?? undefined;
this.element.focus();
}
hover_next(): void {
this.visible.set_val(true, { silent: false });
this.hover_item(
@ -90,6 +97,10 @@ export class Menu<T> extends Widget {
if (this.visible.val != visible) {
this.hover_item();
this.inner_element.scrollTop = 0;
if (!visible && this.previously_focused_element instanceof HTMLElement) {
this.previously_focused_element.focus();
}
}
}
@ -106,24 +117,31 @@ export class Menu<T> extends Widget {
this.select_item(parseInt(index_str, 10));
};
private keydown = (e: Event): void => {
const key = (e as KeyboardEvent).key;
private keydown = (evt: Event): void => {
const key = (evt as KeyboardEvent).key;
switch (key) {
case "ArrowDown":
evt.preventDefault();
this.hover_next();
break;
case "ArrowUp":
evt.preventDefault();
this.hover_prev();
break;
case "Enter":
evt.preventDefault();
this.select_hovered();
break;
}
};
private blur = (): void => {
this.visible.val = false;
};
private inner_mouseover = (e: Event): void => {
if (e.target && e.target instanceof HTMLElement) {
const index = e.target.dataset.index;

View File

@ -53,13 +53,15 @@ export class Select<T> extends LabelledControl {
this.just_opened = false;
this.disposables(
disposable_listener(this.button.element, "mousedown", e => this.button_mousedown(e)),
disposable_listener(this.button.element, "mousedown", this.button_mousedown),
this.button.mouseup.observe(() => this.button_mouseup()),
this.button.mouseup.observe(this.button_mouseup),
this.menu.selected.observe(({ value }) =>
this._selected.set_val(value, { silent: false }),
),
this.button.keydown.observe(this.button_keydown),
this.menu.selected.observe(({ value }) => {
this._selected.set_val(value, { silent: false });
}),
);
if (options) {
@ -83,13 +85,13 @@ export class Select<T> extends LabelledControl {
this.menu.selected.val = selected;
}
private button_mousedown(e: Event): void {
private button_mousedown = (e: Event): void => {
e.stopPropagation();
this.just_opened = !this.menu.visible.val;
this.menu.visible.val = true;
}
};
private button_mouseup(): void {
private button_mouseup = (): void => {
if (this.just_opened) {
this.menu.focus();
} else {
@ -97,5 +99,15 @@ export class Select<T> extends LabelledControl {
}
this.just_opened = false;
};
private button_keydown = ({ value: evt }: { value: KeyboardEvent }): void => {
if (evt.key === "Enter" || evt.key === " ") {
evt.preventDefault();
this.just_opened = !this.menu.visible.val;
this.menu.visible.val = true;
this.menu.focus();
this.menu.hover_next();
}
};
}

View File

@ -22,6 +22,7 @@
--control-border: solid 1px hsl(0, 0%, 10%);
--control-inner-border: solid 1px hsl(0, 0%, 35%);
--control-inner-border-focus: solid 1px hsl(0, 0%, 50%);
/* Inputs */

View File

@ -1 +1 @@
35
36