lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
244 lines (243 loc) • 10.8 kB
TypeScript
import { FocusType } from '../core/FocusType.js';
import type { Widget } from '../widgets/Widget.js';
import type { Driver } from '../core/Driver.js';
import type { Root } from '../core/Root.js';
import type { CaptureList } from '../core/CaptureList.js';
/**
* A group of Roots. When a {@link TabSelectEvent} is not captured by a
* {@link Root} in a group, the TabSelectEvent is carried over to the next (or
* previous, depending on the direction) root in the group. Although
* TabSelectEvent events are carried over between Roots in the same group, they
* are **not** automatically carried over **between different groups**.
*
* This behaviour is useful for binding Roots to DOM elements. For example,
* {@link DOMRoot | DOMRoots} should be placed in groups with a single DOMRoot,
* since the DOMRoot owns its own DOM element, but Roots used for an external 3D
* engine, where the roots share a single DOM element such as a canvas used as
* the output for rendering, should all be in the same group.
*
* @category Driver
*/
export interface KeyboardDriverGroup {
/**
* The list of {@link Root | Roots} assigned to this group, in the order
* they were added to this group. Not to be confused with
* {@link KeyboardDriver#accessList}.
*/
roots: Array<Root>;
/**
* Similar to {@link KeyboardDriverGroup#roots}, but only contains enabled
* {@link Root | Roots}.
*/
enabledRoots: Array<Root>;
/**
* Similar to {@link KeyboardDriverGroup#enabledRoots}, but only contains
* enabled {@link Root | Roots} that are {@link Root#tabFocusable}.
*/
tabbableRoots: Array<Root>;
/**
* Should {@link TabSelectEvent} events wrap-around to the other end of the
* group if not captured by the last (or first) {@link Root}? If this is
* true, the navigation will be trapped to this group for keyboard-only
* users, since that will be the only way to change keyboard focus. Useful
* for 3D engines where all Roots share the same canvas.
*/
wrapsAround: boolean;
}
/**
* Options used for creating a new {@link KeyboardDriverGroup}.
*
* @category Driver
*/
export interface KeyboardDriverGroupOptions {
/** See {@link KeyboardDriverGroup#wrapsAround}. */
wrapsAround: boolean;
}
/**
* A generic keyboard {@link Driver | driver}.
*
* Does nothing on its own, but provides an API for sending keyboard events to
* registered roots.
*
* @category Driver
*/
export declare class KeyboardDriver<G extends KeyboardDriverGroup = KeyboardDriverGroup, O extends KeyboardDriverGroupOptions = KeyboardDriverGroupOptions> implements Driver {
/**
* Groups belonging to this driver. Roots in the same group transfer tab
* selections between themselves. Do not modify from a child class.
*/
protected readonly groups: G[];
/**
* A map from a Root to a group. Used only for optimisation purposes. Do not
* modify from a child class.
*/
protected readonly groupMap: Map<Root, G>;
/**
* The list of {@link Root | Roots} that are using this driver, in the order
* of access; the last focused (with any focus type) Root is moved to the
* beginning of the list.
*
* Used as a fallback for {@link KeyboardDriver#focus}.
*/
private accessList;
/** A set containing the keys currently down. */
private keysDown;
/** The currently focused root. New keyboard events will go to this root */
private focus;
/**
* Changes the current {@link KeyboardDriver#focus | root focus}.
*
* If there was a previous root focus, that root's {@link Root#clearFocus}
* is called with {@link FocusType#Keyboard}.
*
* {@link KeyboardDriver#keysDown} is cleared.
*/
protected changeFocusedRoot(root: Root | null): void;
/**
* Get the current {@link KeyboardDriver#focus | root focus}.
*
* @returns Returns {@link KeyboardDriver#focus}
*/
getFocusedRoot(): Root | null;
/**
* Similar to {@link KeyboardDriver#getFocusedRoot}, but can fall back to
* the first root of {@link KeyboardDriver#accessList} if
* {@link KeyboardDriver#focus} is null.
*/
getEffectiveFocusedRoot(): Root | null;
/**
* Clear the current {@link KeyboardDriver#focus | root focus}. Calls
* {@link KeyboardDriver#changeFocusedRoot} with null.
*/
clearFocus(): void;
/**
* Dispatch a new {@link KeyPressEvent} event to the
* {@link KeyboardDriver#getEffectiveFocusedRoot | effective focused Root}.
*
* @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API.
* @param shift - Is shift being pressed?
* @param ctrl - Is control being pressed?
* @param alt - Is alt being pressed?
* @param virtual - Is the key down originating from a virtual keyboard? False by default
* @returns Returns a list of dispatched events and whether they were captured.
*/
keyDown(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList;
/**
* Dispatch a new {@link KeyReleaseEvent} event to the
* {@link KeyboardDriver#getEffectiveFocusedRoot | effective focused Root}.
*
* @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API.
* @param shift - Is shift being pressed?
* @param ctrl - Is control being pressed?
* @param alt - Is alt being pressed?
* @param virtual - Is the key up originating from a virtual keyboard? False by default
* @returns Returns a list of dispatched events and whether they were captured.
*/
keyUp(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList;
/**
* Calls {@link KeyboardDriver#keyDown} followed by
* {@link KeyboardDriver#keyUp}. If the key was already down before calling
* ({@link KeyboardDriver#isKeyDown}), keyUp is not called.
*
* @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API.
* @param shift - Is shift being pressed?
* @param ctrl - Is control being pressed?
* @param alt - Is alt being pressed?
* @param virtual - Is the key press originating from a virtual keyboard? False by default
* @returns Returns a list of dispatched events and whether they were captured.
*/
keyPress(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList;
/**
* Check if a key is pressed.
*
* @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API.
*
* @returns Returns true if key was in {@link KeyboardDriver#keysDown}
*/
isKeyDown(key: string): boolean;
/**
* Adds enabled root to {@link KeyboardDriver#accessList} and its respective
* group.
*/
onEnable(root: Root): void;
/**
* Removes disabled root from {@link KeyboardDriver#accessList} and its
* respective group. If the root was the {@link KeyboardDriver#focus}, then
* {@link KeyboardDriver#clearFocus | the focus is cleared }.
*/
onDisable(root: Root): void;
update(_root: Root): void;
/**
* Does nothing if the new focus type is not a {@link FocusType.Keyboard}.
* If the focus comes from a root which is not the
* {@link KeyboardDriver#focus | root focus}, then the root focus is
* {@link KeyboardDriver#changeFocusedRoot | changed to the new root}. If
* there is no new focused widget (the root's keyboard focus was cleared),
* then nothing happens.
*
* If a {@link Root} becomes focused (with any focus type, not just keyboard
* focus), it is moved to the beginning of the
* {@link KeyboardDriver#accessList} list.
*
* This behaviour is confusing, however, it's required so that the keyboard
* focus "lingers" for future tab key presses; this way, pressing tab can do
* tab selection even when there is no widget that wants keyboard input.
* When a focus is lingering, then it means that key events are still being
* dispatched to the last focused root, but they don't have a target. This
* way, most events get dropped, but tab key events are used for tab
* selection.
*/
onFocusChanged(root: Root, focusType: FocusType, newFocus: Widget | null): void;
onFocusCapturerChanged(_root: Root, _focusType: FocusType, _oldCapturer: Widget | null, _newCapturer: Widget | null): void;
/**
* Check if the currently focused root needs keyboard input. Virtual
* keyboard should query this property to know when to show themselves.
*/
get needsInput(): boolean;
/**
* Dispatches an event to the currently focused root (or a fallback).
* Handles wrap-around for tab selection. Internal use only.
*
* @param event - The event to dispatch
*/
private dispatchEvent;
/** Get the index of a group in the groups list. For internal use only */
private getGroupIndex;
/**
* Get the group that a {@link Root} is assigned to. Throws an error if the
* Root is not assigned to any group in this driver.
*
* @returns Returns the group assigned to this Root. The group is live, do not modify it directly.
*/
getGroup(root: Root): G;
/**
* Get a new group, with no {@link Root | Roots}.
*
* @returns Returns the created group. The group is live, do not modify it directly.
*/
createGroup(options: O): G;
/**
* Delete a group that is assigned to this keyboard. Throws an error if the
* group is still in use (has assigned {@link Root | Roots}).
*/
deleteGroup(group: G): void;
/**
* Bind a {@link Root} to a group that is assigned to this keyboard. Throws
* an error if the Root is already assigned to a group in this driver.
*/
bindRoot(root: Root, group: G): void;
/**
* Unbind a {@link Root} from its assigned group. Throws an error if the
* Root is not assigned to any group in this driver.
*/
unbindGroup(root: Root): void;
/**
* Bind a {@link Root} to this keyboard, in a new group dedicated to the
* Root. Equivalent to creating a new group and binding a Root to it. Useful
* if you are using lazy-widgets directly in the DOM, where each Root has a
* dedicated DOM element.
*
* @returns Returns the created group. The group is live, do not modify it directly.
*/
bindSingletRoot(root: Root, options: O): G;
}