Skip to content

Commit

Permalink
Adding globalkeydown/globalkeyup input events, see #1445
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Oct 26, 2022
1 parent a6c4c80 commit 7afd213
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 13 deletions.
70 changes: 57 additions & 13 deletions js/input/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@
* - keyup : Triggered for all keys when released. When a screen reader is active, this event will be omitted
* role="button" is activated.
* See https://www.w3.org/TR/DOM-Level-3-Events/#keyup
* - globalkeydown: Triggered for all keys pressed, regardless of whether the Node has focus. It just needs to be
* visible, inputEnabled, and all of its ancestors visible and inputEnabled.
* - globalkeyup: Triggered for all keys released, regardless of whether the Node has focus. It just needs to be
* visible, inputEnabled, and all of its ancestors visible and inputEnabled.
*
*
* *** Event Dispatch
*
Expand Down Expand Up @@ -841,11 +846,23 @@ export default class Input extends PhetioObject {
const notBlockingSubsequentClicksOccurringTooQuickly = trail && !( eventName === 'click' &&
_.some( trail.nodes, node => node.positionInPDOM ) &&
event.timeStamp - this.upTimeStamp <= PDOM_CLICK_DELAY );

if ( eventName === 'keydown' ) {
this.dispatchGlobalEvent<KeyboardEvent>( 'globalkeydown', event as KeyboardEvent, true );
}
if ( eventName === 'keyup' ) {
this.dispatchGlobalEvent<KeyboardEvent>( 'globalkeyup', event as KeyboardEvent, true );
}

if ( trail && notBlockingSubsequentClicksOccurringTooQuickly ) {
( this[ actionName as keyof Input ] as unknown as PhetioAction<[ Event ]> ).execute( event );
}

if ( eventName === 'keydown' ) {
this.dispatchGlobalEvent<KeyboardEvent>( 'globalkeydown', event as KeyboardEvent, false );
}
if ( eventName === 'keyup' ) {
this.dispatchGlobalEvent<KeyboardEvent>( 'globalkeyup', event as KeyboardEvent, false );
}
}

sceneryLog && sceneryLog.InputEvent && sceneryLog.pop();
Expand Down Expand Up @@ -1136,6 +1153,31 @@ export default class Input extends PhetioObject {
}
}

private dispatchGlobalEvent<DOMEvent extends Event>( eventType: string, domEvent: DOMEvent, capture: boolean ): void {

this.ensurePDOMPointer();
assert && assert( this.pdomPointer );
const pointer = this.pdomPointer!;
const inputEvent = new SceneryEvent<DOMEvent>( new Trail(), eventType, pointer, domEvent );

const recursiveGlobalDispatch = ( node: Node ) => {
if ( !node.isDisposed && node.isVisible() && node.isInputEnabled() ) {
// Reverse iteration follows the z-order from "visually in front" to "visually in back" like normal dipatch
for ( let i = node._children.length - 1; i >= 0; i-- ) {
recursiveGlobalDispatch( node._children[ i ] );
}

if ( !inputEvent.aborted && !inputEvent.handled ) {
// Notification of ourself AFTER our children results in the depth-first scan.
inputEvent.currentTarget = node;
this.dispatchToListeners<DOMEvent>( pointer, node._inputListeners, eventType, inputEvent, capture );
}
}
};

recursiveGlobalDispatch( this.rootNode );
}

/**
* From a DOM Event, get its relatedTarget and map that to the scenery Node. Will return null if relatedTarget
* is not provided, or if relatedTarget is not under PDOM, or there is no associated Node with trail id on the
Expand Down Expand Up @@ -1781,7 +1823,7 @@ export default class Input extends PhetioObject {
* @param type
* @param inputEvent
*/
private dispatchToListeners<DOMEvent extends Event>( pointer: Pointer, listeners: TInputListener[], type: string, inputEvent: SceneryEvent<DOMEvent> ): void {
private dispatchToListeners<DOMEvent extends Event>( pointer: Pointer, listeners: TInputListener[], type: string, inputEvent: SceneryEvent<DOMEvent>, capture: boolean | null = null ): void {

if ( inputEvent.handled ) {
return;
Expand All @@ -1792,22 +1834,24 @@ export default class Input extends PhetioObject {
for ( let i = 0; i < listeners.length; i++ ) {
const listener = listeners[ i ];

if ( !inputEvent.aborted && listener[ specificType as keyof TInputListener ] ) {
sceneryLog && sceneryLog.EventDispatch && sceneryLog.EventDispatch( specificType );
sceneryLog && sceneryLog.EventDispatch && sceneryLog.push();
if ( capture === null || capture === !!listener.capture ) {
if ( !inputEvent.aborted && listener[ specificType as keyof TInputListener ] ) {
sceneryLog && sceneryLog.EventDispatch && sceneryLog.EventDispatch( specificType );
sceneryLog && sceneryLog.EventDispatch && sceneryLog.push();

( listener[ specificType as keyof TInputListener ] as SceneryListenerFunction<DOMEvent> )( inputEvent );
( listener[ specificType as keyof TInputListener ] as SceneryListenerFunction<DOMEvent> )( inputEvent );

sceneryLog && sceneryLog.EventDispatch && sceneryLog.pop();
}
sceneryLog && sceneryLog.EventDispatch && sceneryLog.pop();
}

if ( !inputEvent.aborted && listener[ type as keyof TInputListener ] ) {
sceneryLog && sceneryLog.EventDispatch && sceneryLog.EventDispatch( type );
sceneryLog && sceneryLog.EventDispatch && sceneryLog.push();
if ( !inputEvent.aborted && listener[ type as keyof TInputListener ] ) {
sceneryLog && sceneryLog.EventDispatch && sceneryLog.EventDispatch( type );
sceneryLog && sceneryLog.EventDispatch && sceneryLog.push();

( listener[ type as keyof TInputListener ] as SceneryListenerFunction<DOMEvent> )( inputEvent );
( listener[ type as keyof TInputListener ] as SceneryListenerFunction<DOMEvent> )( inputEvent );

sceneryLog && sceneryLog.EventDispatch && sceneryLog.pop();
sceneryLog && sceneryLog.EventDispatch && sceneryLog.pop();
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions js/input/TInputListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type SceneryListenerFunction<T extends Event = Event> = ( event: SceneryE
type TInputListener = {
interrupt?: () => void;
cursor?: string | null;
capture?: boolean; // NOTE: only applies to globalkeydown / globalkeyup

focus?: SceneryListenerFunction<FocusEvent>;
blur?: SceneryListenerFunction<FocusEvent>;
Expand All @@ -23,6 +24,9 @@ type TInputListener = {
keydown?: SceneryListenerFunction<KeyboardEvent>;
keyup?: SceneryListenerFunction<KeyboardEvent>;

globalkeydown?: SceneryListenerFunction<KeyboardEvent>;
globalkeyup?: SceneryListenerFunction<KeyboardEvent>;

click?: SceneryListenerFunction<MouseEvent>;
input?: SceneryListenerFunction<Event | InputEvent>;
change?: SceneryListenerFunction;
Expand Down

0 comments on commit 7afd213

Please sign in to comment.