Files
CHOMPStation2/tgui/packages/tgui_ch/events.js
2023-05-23 17:43:01 +02:00

220 lines
5.0 KiB
JavaScript

/**
* Normalized browser focus events and BYOND-specific focus helpers.
*
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { EventEmitter } from 'common/events';
import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes';
export const globalEvents = new EventEmitter();
let ignoreWindowFocus = false;
export const setupGlobalEvents = (options = {}) => {
ignoreWindowFocus = !!options.ignoreWindowFocus;
};
// Window focus
// --------------------------------------------------------
let windowFocusTimeout;
let windowFocused = true;
const setWindowFocus = (value, delayed) => {
// Pretend to always be in focus.
if (ignoreWindowFocus) {
windowFocused = true;
return;
}
if (windowFocusTimeout) {
clearTimeout(windowFocusTimeout);
windowFocusTimeout = null;
}
if (delayed) {
windowFocusTimeout = setTimeout(() => setWindowFocus(value));
return;
}
if (windowFocused !== value) {
windowFocused = value;
globalEvents.emit(value ? 'window-focus' : 'window-blur');
globalEvents.emit('window-focus-change', value);
}
};
// Focus stealing
// --------------------------------------------------------
let focusStolenBy = null;
export const canStealFocus = (node) => {
const tag = String(node.tagName).toLowerCase();
return tag === 'input' || tag === 'textarea';
};
const stealFocus = (node) => {
releaseStolenFocus();
focusStolenBy = node;
focusStolenBy.addEventListener('blur', releaseStolenFocus);
};
const releaseStolenFocus = () => {
if (focusStolenBy) {
focusStolenBy.removeEventListener('blur', releaseStolenFocus);
focusStolenBy = null;
}
};
// Focus follows the mouse
// --------------------------------------------------------
let focusedNode = null;
let lastVisitedNode = null;
const trackedNodes = [];
export const addScrollableNode = (node) => {
trackedNodes.push(node);
};
export const removeScrollableNode = (node) => {
const index = trackedNodes.indexOf(node);
if (index >= 0) {
trackedNodes.splice(index, 1);
}
};
const focusNearestTrackedParent = (node) => {
if (focusStolenBy || !windowFocused) {
return;
}
const body = document.body;
while (node && node !== body) {
if (trackedNodes.includes(node)) {
// NOTE: Contains is a DOM4 method - VORESTATION REMOVAL
// if (node.contains(focusedNode)) {
// return;
// }
focusedNode = node;
node.focus();
return;
}
node = node.parentNode;
}
};
window.addEventListener('mousemove', (e) => {
const node = e.target;
if (node !== lastVisitedNode) {
lastVisitedNode = node;
focusNearestTrackedParent(node);
}
});
// Focus event hooks
// --------------------------------------------------------
window.addEventListener('focusin', (e) => {
lastVisitedNode = null;
focusedNode = e.target;
setWindowFocus(true);
if (canStealFocus(e.target)) {
stealFocus(e.target);
return;
}
});
window.addEventListener('focusout', (e) => {
lastVisitedNode = null;
setWindowFocus(false, true);
});
window.addEventListener('blur', (e) => {
lastVisitedNode = null;
setWindowFocus(false, true);
});
window.addEventListener('beforeunload', (e) => {
setWindowFocus(false);
});
// Key events
// --------------------------------------------------------
const keyHeldByCode = {};
export class KeyEvent {
constructor(e, type, repeat) {
this.event = e;
this.type = type;
this.code = window.event ? e.which : e.keyCode;
this.ctrl = e.ctrlKey;
this.shift = e.shiftKey;
this.alt = e.altKey;
this.repeat = !!repeat;
}
hasModifierKeys() {
return this.ctrl || this.alt || this.shift;
}
isModifierKey() {
return this.code === KEY_CTRL || this.code === KEY_SHIFT || this.code === KEY_ALT;
}
isDown() {
return this.type === 'keydown';
}
isUp() {
return this.type === 'keyup';
}
toString() {
if (this._str) {
return this._str;
}
this._str = '';
if (this.ctrl) {
this._str += 'Ctrl+';
}
if (this.alt) {
this._str += 'Alt+';
}
if (this.shift) {
this._str += 'Shift+';
}
if (this.code >= 48 && this.code <= 90) {
this._str += String.fromCharCode(this.code);
} else if (this.code >= KEY_F1 && this.code <= KEY_F12) {
this._str += 'F' + (this.code - 111);
} else {
this._str += '[' + this.code + ']';
}
return this._str;
}
}
// IE8: Keydown event is only available on document.
document.addEventListener('keydown', (e) => {
if (canStealFocus(e.target)) {
return;
}
const code = e.keyCode;
const key = new KeyEvent(e, 'keydown', keyHeldByCode[code]);
globalEvents.emit('keydown', key);
globalEvents.emit('key', key);
keyHeldByCode[code] = true;
});
document.addEventListener('keyup', (e) => {
if (canStealFocus(e.target)) {
return;
}
const code = e.keyCode;
const key = new KeyEvent(e, 'keyup');
globalEvents.emit('keyup', key);
globalEvents.emit('key', key);
keyHeldByCode[code] = false;
});