tgui-core 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/{src/components → components}/AnimatedNumber.tsx +185 -185
  2. package/{src/components → components}/BlockQuote.tsx +15 -15
  3. package/{src/components → components}/BodyZoneSelector.tsx +149 -149
  4. package/{src/components → components}/Box.tsx +255 -255
  5. package/{src/components → components}/Button.tsx +415 -415
  6. package/{src/components → components}/ByondUi.jsx +121 -121
  7. package/{src/components → components}/Chart.tsx +160 -160
  8. package/{src/components → components}/ColorBox.tsx +30 -30
  9. package/{src/components → components}/Dimmer.tsx +19 -19
  10. package/{src/components → components}/Divider.tsx +26 -26
  11. package/{src/components → components}/DmIcon.tsx +72 -72
  12. package/{src/components → components}/DraggableControl.jsx +282 -282
  13. package/{src/components → components}/Dropdown.tsx +246 -246
  14. package/{src/components → components}/Flex.tsx +105 -105
  15. package/{src/components → components}/Icon.tsx +91 -91
  16. package/{src/components → components}/Input.tsx +181 -181
  17. package/{src/components → components}/KeyListener.tsx +40 -40
  18. package/{src/components → components}/Knob.tsx +185 -185
  19. package/{src/components → components}/LabeledList.tsx +130 -130
  20. package/{src/components → components}/MenuBar.tsx +233 -238
  21. package/{src/components → components}/Modal.tsx +25 -25
  22. package/{src/components → components}/NoticeBox.tsx +48 -48
  23. package/{src/components → components}/NumberInput.tsx +328 -328
  24. package/{src/components → components}/ProgressBar.tsx +79 -79
  25. package/{src/components → components}/RestrictedInput.jsx +301 -301
  26. package/{src/components → components}/RoundGauge.tsx +189 -189
  27. package/{src/components → components}/Section.tsx +125 -125
  28. package/{src/components → components}/Slider.tsx +173 -173
  29. package/{src/components → components}/Stack.tsx +101 -101
  30. package/{src/components → components}/Table.tsx +90 -90
  31. package/{src/components → components}/Tabs.tsx +90 -90
  32. package/{src/components → components}/TextArea.tsx +198 -198
  33. package/{src/components → components}/TimeDisplay.jsx +64 -64
  34. package/components/index.ts +51 -0
  35. package/{src/debug/KitchenSink.jsx → debug/KitchenSink.tsx} +56 -56
  36. package/{src/debug/actions.js → debug/actions.ts} +11 -11
  37. package/{src/debug/hooks.js → debug/hooks.ts} +10 -10
  38. package/{src/debug/middleware.js → debug/middleware.ts} +67 -86
  39. package/{src/debug/reducer.js → debug/reducer.ts} +27 -22
  40. package/{src/debug/selectors.js → debug/selectors.ts} +7 -7
  41. package/{src/layouts → layouts}/Layout.tsx +75 -75
  42. package/{src/layouts → layouts}/NtosWindow.tsx +162 -162
  43. package/{src/layouts → layouts}/Pane.tsx +56 -56
  44. package/{src/layouts → layouts}/Window.tsx +227 -227
  45. package/layouts/index.ts +10 -0
  46. package/package.json +3 -2
  47. package/src/assets.ts +43 -43
  48. package/src/backend.ts +368 -369
  49. package/src/drag.ts +280 -280
  50. package/src/events.ts +237 -237
  51. package/src/hotkeys.ts +212 -212
  52. package/src/renderer.ts +50 -50
  53. package/stories/Blink.stories.tsx +20 -0
  54. package/stories/BlockQuote.stories.tsx +23 -0
  55. package/stories/Box.stories.tsx +27 -0
  56. package/stories/Button.stories.tsx +68 -0
  57. package/stories/ByondUi.stories.tsx +45 -0
  58. package/stories/Collapsible.stories.tsx +23 -0
  59. package/stories/Flex.stories.tsx +68 -0
  60. package/stories/Input.stories.tsx +124 -0
  61. package/stories/LabeledList.stories.tsx +73 -0
  62. package/stories/Popper.stories.tsx +58 -0
  63. package/stories/ProgressBar.stories.tsx +58 -0
  64. package/stories/Stack.stories.tsx +55 -0
  65. package/stories/Storage.stories.tsx +46 -0
  66. package/stories/Themes.stories.tsx +30 -0
  67. package/stories/Tooltip.stories.tsx +48 -0
  68. package/stories/common.tsx +19 -0
  69. package/tsconfig.json +0 -21
  70. package/src/components/Grid.tsx +0 -44
  71. /package/{src/common → common}/collections.ts +0 -0
  72. /package/{src/common → common}/color.ts +0 -0
  73. /package/{src/common → common}/events.ts +0 -0
  74. /package/{src/common → common}/exhaustive.ts +0 -0
  75. /package/{src/common → common}/fp.ts +0 -0
  76. /package/{src/common → common}/keycodes.ts +0 -0
  77. /package/{src/common → common}/keys.ts +0 -0
  78. /package/{src/common → common}/math.ts +0 -0
  79. /package/{src/common → common}/perf.ts +0 -0
  80. /package/{src/common → common}/random.ts +0 -0
  81. /package/{src/common → common}/react.ts +0 -0
  82. /package/{src/common → common}/redux.ts +0 -0
  83. /package/{src/common → common}/storage.js +0 -0
  84. /package/{src/common → common}/string.ts +0 -0
  85. /package/{src/common → common}/timer.ts +0 -0
  86. /package/{src/common → common}/type-utils.ts +0 -0
  87. /package/{src/common → common}/types.ts +0 -0
  88. /package/{src/common → common}/uuid.ts +0 -0
  89. /package/{src/common → common}/vector.ts +0 -0
  90. /package/{src/components → components}/Autofocus.tsx +0 -0
  91. /package/{src/components → components}/Blink.jsx +0 -0
  92. /package/{src/components → components}/Collapsible.tsx +0 -0
  93. /package/{src/components → components}/Dialog.tsx +0 -0
  94. /package/{src/components → components}/FakeTerminal.jsx +0 -0
  95. /package/{src/components → components}/FitText.tsx +0 -0
  96. /package/{src/components → components}/Image.tsx +0 -0
  97. /package/{src/components → components}/InfinitePlane.jsx +0 -0
  98. /package/{src/components → components}/LabeledControls.tsx +0 -0
  99. /package/{src/components → components}/Popper.tsx +0 -0
  100. /package/{src/components → components}/StyleableSection.tsx +0 -0
  101. /package/{src/components → components}/Tooltip.tsx +0 -0
  102. /package/{src/components → components}/TrackOutsideClicks.tsx +0 -0
  103. /package/{src/components → components}/VirtualList.tsx +0 -0
  104. /package/{src/debug → debug}/index.ts +0 -0
  105. /package/{src/styles → styles}/base.scss +0 -0
  106. /package/{src/styles → styles}/colors.scss +0 -0
  107. /package/{src/styles → styles}/components/BlockQuote.scss +0 -0
  108. /package/{src/styles → styles}/components/Button.scss +0 -0
  109. /package/{src/styles → styles}/components/ColorBox.scss +0 -0
  110. /package/{src/styles → styles}/components/Dialog.scss +0 -0
  111. /package/{src/styles → styles}/components/Dimmer.scss +0 -0
  112. /package/{src/styles → styles}/components/Divider.scss +0 -0
  113. /package/{src/styles → styles}/components/Dropdown.scss +0 -0
  114. /package/{src/styles → styles}/components/Flex.scss +0 -0
  115. /package/{src/styles → styles}/components/Icon.scss +0 -0
  116. /package/{src/styles → styles}/components/Input.scss +0 -0
  117. /package/{src/styles → styles}/components/Knob.scss +0 -0
  118. /package/{src/styles → styles}/components/LabeledList.scss +0 -0
  119. /package/{src/styles → styles}/components/MenuBar.scss +0 -0
  120. /package/{src/styles → styles}/components/Modal.scss +0 -0
  121. /package/{src/styles → styles}/components/NoticeBox.scss +0 -0
  122. /package/{src/styles → styles}/components/NumberInput.scss +0 -0
  123. /package/{src/styles → styles}/components/ProgressBar.scss +0 -0
  124. /package/{src/styles → styles}/components/RoundGauge.scss +0 -0
  125. /package/{src/styles → styles}/components/Section.scss +0 -0
  126. /package/{src/styles → styles}/components/Slider.scss +0 -0
  127. /package/{src/styles → styles}/components/Stack.scss +0 -0
  128. /package/{src/styles → styles}/components/Table.scss +0 -0
  129. /package/{src/styles → styles}/components/Tabs.scss +0 -0
  130. /package/{src/styles → styles}/components/TextArea.scss +0 -0
  131. /package/{src/styles → styles}/components/Tooltip.scss +0 -0
  132. /package/{src/styles → styles}/functions.scss +0 -0
  133. /package/{src/styles → styles}/layouts/Layout.scss +0 -0
  134. /package/{src/styles → styles}/layouts/NtosHeader.scss +0 -0
  135. /package/{src/styles → styles}/layouts/NtosWindow.scss +0 -0
  136. /package/{src/styles → styles}/layouts/TitleBar.scss +0 -0
  137. /package/{src/styles → styles}/layouts/Window.scss +0 -0
  138. /package/{src/styles → styles}/main.scss +0 -0
  139. /package/{src/styles → styles}/reset.scss +0 -0
  140. /package/{src/styles → styles}/themes/abductor.scss +0 -0
  141. /package/{src/styles → styles}/themes/admin.scss +0 -0
  142. /package/{src/styles → styles}/themes/cardtable.scss +0 -0
  143. /package/{src/styles → styles}/themes/hackerman.scss +0 -0
  144. /package/{src/styles → styles}/themes/malfunction.scss +0 -0
  145. /package/{src/styles → styles}/themes/neutral.scss +0 -0
  146. /package/{src/styles → styles}/themes/ntOS95.scss +0 -0
  147. /package/{src/styles → styles}/themes/ntos.scss +0 -0
  148. /package/{src/styles → styles}/themes/ntos_cat.scss +0 -0
  149. /package/{src/styles → styles}/themes/ntos_darkmode.scss +0 -0
  150. /package/{src/styles → styles}/themes/ntos_lightmode.scss +0 -0
  151. /package/{src/styles → styles}/themes/ntos_spooky.scss +0 -0
  152. /package/{src/styles → styles}/themes/ntos_synth.scss +0 -0
  153. /package/{src/styles → styles}/themes/ntos_terminal.scss +0 -0
  154. /package/{src/styles → styles}/themes/paper.scss +0 -0
  155. /package/{src/styles → styles}/themes/retro.scss +0 -0
  156. /package/{src/styles → styles}/themes/spookyconsole.scss +0 -0
  157. /package/{src/styles → styles}/themes/syndicate.scss +0 -0
  158. /package/{src/styles → styles}/themes/wizard.scss +0 -0
package/src/events.ts CHANGED
@@ -1,237 +1,237 @@
1
- /**
2
- * Normalized browser focus events and BYOND-specific focus helpers.
3
- *
4
- * @file
5
- * @copyright 2020 Aleksej Komarov
6
- * @license MIT
7
- */
8
-
9
- import { EventEmitter } from './common/events';
10
- import {
11
- KEY_ALT,
12
- KEY_CTRL,
13
- KEY_F1,
14
- KEY_F12,
15
- KEY_SHIFT,
16
- } from './common/keycodes';
17
-
18
- export const globalEvents = new EventEmitter();
19
- let ignoreWindowFocus = false;
20
-
21
- export const setupGlobalEvents = (
22
- options: { ignoreWindowFocus?: boolean } = {}
23
- ): void => {
24
- ignoreWindowFocus = !!options.ignoreWindowFocus;
25
- };
26
-
27
- // Window focus
28
- // --------------------------------------------------------
29
-
30
- let windowFocusTimeout: ReturnType<typeof setTimeout> | null;
31
- let windowFocused = true;
32
-
33
- // Pretend to always be in focus.
34
- const setWindowFocus = (value: boolean, delayed?: boolean) => {
35
- if (ignoreWindowFocus) {
36
- windowFocused = true;
37
- return;
38
- }
39
- if (windowFocusTimeout) {
40
- clearTimeout(windowFocusTimeout);
41
- windowFocusTimeout = null;
42
- }
43
- if (delayed) {
44
- windowFocusTimeout = setTimeout(() => setWindowFocus(value));
45
- return;
46
- }
47
- if (windowFocused !== value) {
48
- windowFocused = value;
49
- globalEvents.emit(value ? 'window-focus' : 'window-blur');
50
- globalEvents.emit('window-focus-change', value);
51
- }
52
- };
53
-
54
- // Focus stealing
55
- // --------------------------------------------------------
56
-
57
- let focusStolenBy: HTMLElement | null = null;
58
-
59
- export const canStealFocus = (node: HTMLElement) => {
60
- const tag = String(node.tagName).toLowerCase();
61
- return tag === 'input' || tag === 'textarea';
62
- };
63
-
64
- const stealFocus = (node: HTMLElement) => {
65
- releaseStolenFocus();
66
- focusStolenBy = node;
67
- focusStolenBy.addEventListener('blur', releaseStolenFocus);
68
- };
69
-
70
- const releaseStolenFocus = () => {
71
- if (focusStolenBy) {
72
- focusStolenBy.removeEventListener('blur', releaseStolenFocus);
73
- focusStolenBy = null;
74
- }
75
- };
76
-
77
- // Focus follows the mouse
78
- // --------------------------------------------------------
79
-
80
- let focusedNode: HTMLElement | null = null;
81
- let lastVisitedNode: HTMLElement | null = null;
82
- const trackedNodes: HTMLElement[] = [];
83
-
84
- export const addScrollableNode = (node: HTMLElement) => {
85
- trackedNodes.push(node);
86
- };
87
-
88
- export const removeScrollableNode = (node: HTMLElement) => {
89
- const index = trackedNodes.indexOf(node);
90
- if (index >= 0) {
91
- trackedNodes.splice(index, 1);
92
- }
93
- };
94
-
95
- const focusNearestTrackedParent = (node: HTMLElement | null) => {
96
- if (focusStolenBy || !windowFocused) {
97
- return;
98
- }
99
- const body = document.body;
100
- while (node && node !== body) {
101
- if (trackedNodes.includes(node)) {
102
- // NOTE: Contains is a DOM4 method
103
- if (node.contains(focusedNode)) {
104
- return;
105
- }
106
- focusedNode = node;
107
- node.focus();
108
- return;
109
- }
110
- node = node.parentElement;
111
- }
112
- };
113
-
114
- window.addEventListener('mousemove', (e) => {
115
- const node = e.target as HTMLElement;
116
- if (node !== lastVisitedNode) {
117
- lastVisitedNode = node;
118
- focusNearestTrackedParent(node);
119
- }
120
- });
121
-
122
- // Focus event hooks
123
- // --------------------------------------------------------
124
-
125
- window.addEventListener('focusin', (e) => {
126
- lastVisitedNode = null;
127
- focusedNode = e.target as HTMLElement;
128
- setWindowFocus(true);
129
- if (canStealFocus(e.target as HTMLElement)) {
130
- stealFocus(e.target as HTMLElement);
131
- }
132
- });
133
-
134
- window.addEventListener('focusout', (e) => {
135
- lastVisitedNode = null;
136
- setWindowFocus(false, true);
137
- });
138
-
139
- window.addEventListener('blur', (e) => {
140
- lastVisitedNode = null;
141
- setWindowFocus(false, true);
142
- });
143
-
144
- window.addEventListener('beforeunload', (e) => {
145
- setWindowFocus(false);
146
- });
147
-
148
- // Key events
149
- // --------------------------------------------------------
150
-
151
- const keyHeldByCode: Record<number, boolean> = {};
152
-
153
- export class KeyEvent {
154
- event: KeyboardEvent;
155
- type: 'keydown' | 'keyup';
156
- code: number;
157
- ctrl: boolean;
158
- shift: boolean;
159
- alt: boolean;
160
- repeat: boolean;
161
- _str?: string;
162
-
163
- constructor(e: KeyboardEvent, type: 'keydown' | 'keyup', repeat?: boolean) {
164
- this.event = e;
165
- this.type = type;
166
- this.code = e.keyCode;
167
- this.ctrl = e.ctrlKey;
168
- this.shift = e.shiftKey;
169
- this.alt = e.altKey;
170
- this.repeat = !!repeat;
171
- }
172
-
173
- hasModifierKeys() {
174
- return this.ctrl || this.alt || this.shift;
175
- }
176
-
177
- isModifierKey() {
178
- return (
179
- this.code === KEY_CTRL || this.code === KEY_SHIFT || this.code === KEY_ALT
180
- );
181
- }
182
-
183
- isDown() {
184
- return this.type === 'keydown';
185
- }
186
-
187
- isUp() {
188
- return this.type === 'keyup';
189
- }
190
-
191
- toString() {
192
- if (this._str) {
193
- return this._str;
194
- }
195
- this._str = '';
196
- if (this.ctrl) {
197
- this._str += 'Ctrl+';
198
- }
199
- if (this.alt) {
200
- this._str += 'Alt+';
201
- }
202
- if (this.shift) {
203
- this._str += 'Shift+';
204
- }
205
- if (this.code >= 48 && this.code <= 90) {
206
- this._str += String.fromCharCode(this.code);
207
- } else if (this.code >= KEY_F1 && this.code <= KEY_F12) {
208
- this._str += 'F' + (this.code - 111);
209
- } else {
210
- this._str += '[' + this.code + ']';
211
- }
212
- return this._str;
213
- }
214
- }
215
-
216
- // IE8: Keydown event is only available on document.
217
- document.addEventListener('keydown', (e) => {
218
- if (canStealFocus(e.target as HTMLElement)) {
219
- return;
220
- }
221
- const code = e.keyCode;
222
- const key = new KeyEvent(e, 'keydown', keyHeldByCode[code]);
223
- globalEvents.emit('keydown', key);
224
- globalEvents.emit('key', key);
225
- keyHeldByCode[code] = true;
226
- });
227
-
228
- document.addEventListener('keyup', (e) => {
229
- if (canStealFocus(e.target as HTMLElement)) {
230
- return;
231
- }
232
- const code = e.keyCode;
233
- const key = new KeyEvent(e, 'keyup');
234
- globalEvents.emit('keyup', key);
235
- globalEvents.emit('key', key);
236
- keyHeldByCode[code] = false;
237
- });
1
+ /**
2
+ * Normalized browser focus events and BYOND-specific focus helpers.
3
+ *
4
+ * @file
5
+ * @copyright 2020 Aleksej Komarov
6
+ * @license MIT
7
+ */
8
+
9
+ import { EventEmitter } from '../common/events';
10
+ import {
11
+ KEY_ALT,
12
+ KEY_CTRL,
13
+ KEY_F1,
14
+ KEY_F12,
15
+ KEY_SHIFT,
16
+ } from '../common/keycodes';
17
+
18
+ export const globalEvents = new EventEmitter();
19
+ let ignoreWindowFocus = false;
20
+
21
+ export const setupGlobalEvents = (
22
+ options: { ignoreWindowFocus?: boolean } = {}
23
+ ): void => {
24
+ ignoreWindowFocus = !!options.ignoreWindowFocus;
25
+ };
26
+
27
+ // Window focus
28
+ // --------------------------------------------------------
29
+
30
+ let windowFocusTimeout: ReturnType<typeof setTimeout> | null;
31
+ let windowFocused = true;
32
+
33
+ // Pretend to always be in focus.
34
+ const setWindowFocus = (value: boolean, delayed?: boolean) => {
35
+ if (ignoreWindowFocus) {
36
+ windowFocused = true;
37
+ return;
38
+ }
39
+ if (windowFocusTimeout) {
40
+ clearTimeout(windowFocusTimeout);
41
+ windowFocusTimeout = null;
42
+ }
43
+ if (delayed) {
44
+ windowFocusTimeout = setTimeout(() => setWindowFocus(value));
45
+ return;
46
+ }
47
+ if (windowFocused !== value) {
48
+ windowFocused = value;
49
+ globalEvents.emit(value ? 'window-focus' : 'window-blur');
50
+ globalEvents.emit('window-focus-change', value);
51
+ }
52
+ };
53
+
54
+ // Focus stealing
55
+ // --------------------------------------------------------
56
+
57
+ let focusStolenBy: HTMLElement | null = null;
58
+
59
+ export const canStealFocus = (node: HTMLElement) => {
60
+ const tag = String(node.tagName).toLowerCase();
61
+ return tag === 'input' || tag === 'textarea';
62
+ };
63
+
64
+ const stealFocus = (node: HTMLElement) => {
65
+ releaseStolenFocus();
66
+ focusStolenBy = node;
67
+ focusStolenBy.addEventListener('blur', releaseStolenFocus);
68
+ };
69
+
70
+ const releaseStolenFocus = () => {
71
+ if (focusStolenBy) {
72
+ focusStolenBy.removeEventListener('blur', releaseStolenFocus);
73
+ focusStolenBy = null;
74
+ }
75
+ };
76
+
77
+ // Focus follows the mouse
78
+ // --------------------------------------------------------
79
+
80
+ let focusedNode: HTMLElement | null = null;
81
+ let lastVisitedNode: HTMLElement | null = null;
82
+ const trackedNodes: HTMLElement[] = [];
83
+
84
+ export const addScrollableNode = (node: HTMLElement) => {
85
+ trackedNodes.push(node);
86
+ };
87
+
88
+ export const removeScrollableNode = (node: HTMLElement) => {
89
+ const index = trackedNodes.indexOf(node);
90
+ if (index >= 0) {
91
+ trackedNodes.splice(index, 1);
92
+ }
93
+ };
94
+
95
+ const focusNearestTrackedParent = (node: HTMLElement | null) => {
96
+ if (focusStolenBy || !windowFocused) {
97
+ return;
98
+ }
99
+ const body = document.body;
100
+ while (node && node !== body) {
101
+ if (trackedNodes.includes(node)) {
102
+ // NOTE: Contains is a DOM4 method
103
+ if (node.contains(focusedNode)) {
104
+ return;
105
+ }
106
+ focusedNode = node;
107
+ node.focus();
108
+ return;
109
+ }
110
+ node = node.parentElement;
111
+ }
112
+ };
113
+
114
+ window.addEventListener('mousemove', (e) => {
115
+ const node = e.target as HTMLElement;
116
+ if (node !== lastVisitedNode) {
117
+ lastVisitedNode = node;
118
+ focusNearestTrackedParent(node);
119
+ }
120
+ });
121
+
122
+ // Focus event hooks
123
+ // --------------------------------------------------------
124
+
125
+ window.addEventListener('focusin', (e) => {
126
+ lastVisitedNode = null;
127
+ focusedNode = e.target as HTMLElement;
128
+ setWindowFocus(true);
129
+ if (canStealFocus(e.target as HTMLElement)) {
130
+ stealFocus(e.target as HTMLElement);
131
+ }
132
+ });
133
+
134
+ window.addEventListener('focusout', (e) => {
135
+ lastVisitedNode = null;
136
+ setWindowFocus(false, true);
137
+ });
138
+
139
+ window.addEventListener('blur', (e) => {
140
+ lastVisitedNode = null;
141
+ setWindowFocus(false, true);
142
+ });
143
+
144
+ window.addEventListener('beforeunload', (e) => {
145
+ setWindowFocus(false);
146
+ });
147
+
148
+ // Key events
149
+ // --------------------------------------------------------
150
+
151
+ const keyHeldByCode: Record<number, boolean> = {};
152
+
153
+ export class KeyEvent {
154
+ event: KeyboardEvent;
155
+ type: 'keydown' | 'keyup';
156
+ code: number;
157
+ ctrl: boolean;
158
+ shift: boolean;
159
+ alt: boolean;
160
+ repeat: boolean;
161
+ _str?: string;
162
+
163
+ constructor(e: KeyboardEvent, type: 'keydown' | 'keyup', repeat?: boolean) {
164
+ this.event = e;
165
+ this.type = type;
166
+ this.code = e.keyCode;
167
+ this.ctrl = e.ctrlKey;
168
+ this.shift = e.shiftKey;
169
+ this.alt = e.altKey;
170
+ this.repeat = !!repeat;
171
+ }
172
+
173
+ hasModifierKeys() {
174
+ return this.ctrl || this.alt || this.shift;
175
+ }
176
+
177
+ isModifierKey() {
178
+ return (
179
+ this.code === KEY_CTRL || this.code === KEY_SHIFT || this.code === KEY_ALT
180
+ );
181
+ }
182
+
183
+ isDown() {
184
+ return this.type === 'keydown';
185
+ }
186
+
187
+ isUp() {
188
+ return this.type === 'keyup';
189
+ }
190
+
191
+ toString() {
192
+ if (this._str) {
193
+ return this._str;
194
+ }
195
+ this._str = '';
196
+ if (this.ctrl) {
197
+ this._str += 'Ctrl+';
198
+ }
199
+ if (this.alt) {
200
+ this._str += 'Alt+';
201
+ }
202
+ if (this.shift) {
203
+ this._str += 'Shift+';
204
+ }
205
+ if (this.code >= 48 && this.code <= 90) {
206
+ this._str += String.fromCharCode(this.code);
207
+ } else if (this.code >= KEY_F1 && this.code <= KEY_F12) {
208
+ this._str += 'F' + (this.code - 111);
209
+ } else {
210
+ this._str += '[' + this.code + ']';
211
+ }
212
+ return this._str;
213
+ }
214
+ }
215
+
216
+ // IE8: Keydown event is only available on document.
217
+ document.addEventListener('keydown', (e) => {
218
+ if (canStealFocus(e.target as HTMLElement)) {
219
+ return;
220
+ }
221
+ const code = e.keyCode;
222
+ const key = new KeyEvent(e, 'keydown', keyHeldByCode[code]);
223
+ globalEvents.emit('keydown', key);
224
+ globalEvents.emit('key', key);
225
+ keyHeldByCode[code] = true;
226
+ });
227
+
228
+ document.addEventListener('keyup', (e) => {
229
+ if (canStealFocus(e.target as HTMLElement)) {
230
+ return;
231
+ }
232
+ const code = e.keyCode;
233
+ const key = new KeyEvent(e, 'keyup');
234
+ globalEvents.emit('keyup', key);
235
+ globalEvents.emit('key', key);
236
+ keyHeldByCode[code] = false;
237
+ });