cdp-skill 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 (78) hide show
  1. package/README.md +3 -0
  2. package/SKILL.md +34 -5
  3. package/package.json +2 -1
  4. package/src/capture/console-capture.js +241 -0
  5. package/src/capture/debug-capture.js +144 -0
  6. package/src/capture/error-aggregator.js +151 -0
  7. package/src/capture/eval-serializer.js +320 -0
  8. package/src/capture/index.js +40 -0
  9. package/src/capture/network-capture.js +211 -0
  10. package/src/capture/pdf-capture.js +256 -0
  11. package/src/capture/screenshot-capture.js +325 -0
  12. package/src/cdp/browser.js +569 -0
  13. package/src/cdp/connection.js +369 -0
  14. package/src/cdp/discovery.js +138 -0
  15. package/src/cdp/index.js +29 -0
  16. package/src/cdp/target-and-session.js +439 -0
  17. package/src/cdp-skill.js +25 -11
  18. package/src/constants.js +79 -0
  19. package/src/dom/actionability.js +638 -0
  20. package/src/dom/click-executor.js +923 -0
  21. package/src/dom/element-handle.js +496 -0
  22. package/src/dom/element-locator.js +475 -0
  23. package/src/dom/element-validator.js +120 -0
  24. package/src/dom/fill-executor.js +489 -0
  25. package/src/dom/index.js +248 -0
  26. package/src/dom/input-emulator.js +406 -0
  27. package/src/dom/keyboard-executor.js +202 -0
  28. package/src/dom/quad-helpers.js +89 -0
  29. package/src/dom/react-filler.js +94 -0
  30. package/src/dom/wait-executor.js +423 -0
  31. package/src/index.js +6 -6
  32. package/src/page/cookie-manager.js +202 -0
  33. package/src/page/dom-stability.js +181 -0
  34. package/src/page/index.js +36 -0
  35. package/src/{page.js → page/page-controller.js} +109 -839
  36. package/src/page/wait-utilities.js +302 -0
  37. package/src/page/web-storage-manager.js +108 -0
  38. package/src/runner/context-helpers.js +224 -0
  39. package/src/runner/execute-browser.js +518 -0
  40. package/src/runner/execute-form.js +315 -0
  41. package/src/runner/execute-input.js +308 -0
  42. package/src/runner/execute-interaction.js +672 -0
  43. package/src/runner/execute-navigation.js +180 -0
  44. package/src/runner/execute-query.js +771 -0
  45. package/src/runner/index.js +51 -0
  46. package/src/runner/step-executors.js +421 -0
  47. package/src/runner/step-validator.js +641 -0
  48. package/src/tests/Actionability.test.js +613 -0
  49. package/src/tests/BrowserClient.test.js +1 -1
  50. package/src/tests/ChromeDiscovery.test.js +1 -1
  51. package/src/tests/ClickExecutor.test.js +554 -0
  52. package/src/tests/ConsoleCapture.test.js +1 -1
  53. package/src/tests/ContextHelpers.test.js +453 -0
  54. package/src/tests/CookieManager.test.js +450 -0
  55. package/src/tests/DebugCapture.test.js +307 -0
  56. package/src/tests/ElementHandle.test.js +1 -1
  57. package/src/tests/ElementLocator.test.js +1 -1
  58. package/src/tests/ErrorAggregator.test.js +1 -1
  59. package/src/tests/EvalSerializer.test.js +391 -0
  60. package/src/tests/FillExecutor.test.js +611 -0
  61. package/src/tests/InputEmulator.test.js +1 -1
  62. package/src/tests/KeyboardExecutor.test.js +430 -0
  63. package/src/tests/NetworkErrorCapture.test.js +1 -1
  64. package/src/tests/PageController.test.js +1 -1
  65. package/src/tests/PdfCapture.test.js +333 -0
  66. package/src/tests/ScreenshotCapture.test.js +1 -1
  67. package/src/tests/SessionRegistry.test.js +1 -1
  68. package/src/tests/StepValidator.test.js +527 -0
  69. package/src/tests/TargetManager.test.js +1 -1
  70. package/src/tests/TestRunner.test.js +1 -1
  71. package/src/tests/WaitStrategy.test.js +1 -1
  72. package/src/tests/WaitUtilities.test.js +508 -0
  73. package/src/tests/WebStorageManager.test.js +333 -0
  74. package/src/types.js +309 -0
  75. package/src/capture.js +0 -1400
  76. package/src/cdp.js +0 -1286
  77. package/src/dom.js +0 -4379
  78. package/src/runner.js +0 -3676
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Target and Session Management Module
3
+ * CDP target management and session registry for browser tabs
4
+ *
5
+ * PUBLIC EXPORTS:
6
+ * - createTargetManager(connection) - Factory for target manager
7
+ * - createSessionRegistry(connection) - Factory for session registry
8
+ * - createPageSession(connection, sessionId, targetId) - Factory for page session
9
+ *
10
+ * @module cdp-skill/cdp/target-and-session
11
+ */
12
+
13
+ /**
14
+ * Create a target manager for browser targets
15
+ * @param {import('../types.js').CDPConnection} connection - CDP connection
16
+ * @returns {Object} Target manager interface
17
+ */
18
+ export function createTargetManager(connection) {
19
+ const targets = new Map();
20
+ let discoveryEnabled = false;
21
+ let boundHandlers = null;
22
+
23
+ function onTargetCreated(params) {
24
+ targets.set(params.targetInfo.targetId, params.targetInfo);
25
+ }
26
+
27
+ function onTargetDestroyed(params) {
28
+ targets.delete(params.targetId);
29
+ }
30
+
31
+ function onTargetInfoChanged(params) {
32
+ targets.set(params.targetInfo.targetId, params.targetInfo);
33
+ }
34
+
35
+ /**
36
+ * Enable automatic target discovery
37
+ * @returns {Promise<void>}
38
+ */
39
+ async function enableDiscovery() {
40
+ if (discoveryEnabled) return;
41
+
42
+ boundHandlers = { onTargetCreated, onTargetDestroyed, onTargetInfoChanged };
43
+ connection.on('Target.targetCreated', boundHandlers.onTargetCreated);
44
+ connection.on('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
45
+ connection.on('Target.targetInfoChanged', boundHandlers.onTargetInfoChanged);
46
+
47
+ await connection.send('Target.setDiscoverTargets', { discover: true });
48
+ discoveryEnabled = true;
49
+ }
50
+
51
+ /**
52
+ * Disable automatic target discovery
53
+ * @returns {Promise<void>}
54
+ */
55
+ async function disableDiscovery() {
56
+ if (!discoveryEnabled) return;
57
+
58
+ await connection.send('Target.setDiscoverTargets', { discover: false });
59
+
60
+ if (boundHandlers) {
61
+ connection.off('Target.targetCreated', boundHandlers.onTargetCreated);
62
+ connection.off('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
63
+ connection.off('Target.targetInfoChanged', boundHandlers.onTargetInfoChanged);
64
+ }
65
+
66
+ discoveryEnabled = false;
67
+ }
68
+
69
+ /**
70
+ * Get all targets, optionally filtered
71
+ * @param {Object} [filter] - Optional target filter
72
+ * @returns {Promise<Array>} Array of target info objects
73
+ */
74
+ async function getTargets(filter = null) {
75
+ const result = await connection.send('Target.getTargets', {
76
+ filter: filter ? [filter] : undefined
77
+ });
78
+
79
+ for (const info of result.targetInfos) {
80
+ targets.set(info.targetId, info);
81
+ }
82
+
83
+ return result.targetInfos;
84
+ }
85
+
86
+ /**
87
+ * Get page targets only
88
+ * @returns {Promise<Array>} Array of page target info objects
89
+ */
90
+ async function getPages() {
91
+ const allTargets = await getTargets();
92
+ return allTargets.filter(t => t.type === 'page');
93
+ }
94
+
95
+ /**
96
+ * Create a new browser target (tab)
97
+ * @param {string} [url='about:blank'] - Initial URL
98
+ * @param {Object} [options] - Creation options
99
+ * @param {number} [options.width] - Viewport width
100
+ * @param {number} [options.height] - Viewport height
101
+ * @param {boolean} [options.background=false] - Create in background
102
+ * @param {boolean} [options.newWindow=false] - Create in new window
103
+ * @returns {Promise<string>} Target ID
104
+ */
105
+ async function createTarget(url = 'about:blank', options = {}) {
106
+ const result = await connection.send('Target.createTarget', {
107
+ url,
108
+ width: options.width,
109
+ height: options.height,
110
+ background: options.background ?? false,
111
+ newWindow: options.newWindow ?? false
112
+ });
113
+ return result.targetId;
114
+ }
115
+
116
+ /**
117
+ * Close a target
118
+ * @param {string} targetId - Target to close
119
+ * @returns {Promise<boolean>} Success
120
+ */
121
+ async function closeTarget(targetId) {
122
+ const result = await connection.send('Target.closeTarget', { targetId });
123
+ targets.delete(targetId);
124
+ return result.success ?? true;
125
+ }
126
+
127
+ /**
128
+ * Activate (bring to front) a target
129
+ * @param {string} targetId - Target to activate
130
+ * @returns {Promise<void>}
131
+ */
132
+ async function activateTarget(targetId) {
133
+ await connection.send('Target.activateTarget', { targetId });
134
+ }
135
+
136
+ /**
137
+ * Get detailed info for a target
138
+ * @param {string} targetId - Target ID
139
+ * @returns {Promise<Object>} Target info
140
+ */
141
+ async function getTargetInfo(targetId) {
142
+ const result = await connection.send('Target.getTargetInfo', { targetId });
143
+ targets.set(targetId, result.targetInfo);
144
+ return result.targetInfo;
145
+ }
146
+
147
+ /**
148
+ * Get cached target info
149
+ * @param {string} targetId - Target ID
150
+ * @returns {Object|undefined} Cached target info
151
+ */
152
+ function getCachedTarget(targetId) {
153
+ return targets.get(targetId);
154
+ }
155
+
156
+ /**
157
+ * Get all cached targets
158
+ * @returns {Map} Cached targets map
159
+ */
160
+ function getCachedTargets() {
161
+ return new Map(targets);
162
+ }
163
+
164
+ /**
165
+ * Clean up target manager
166
+ * @returns {Promise<void>}
167
+ */
168
+ async function cleanup() {
169
+ await disableDiscovery();
170
+ targets.clear();
171
+ }
172
+
173
+ return {
174
+ enableDiscovery,
175
+ disableDiscovery,
176
+ getTargets,
177
+ getPages,
178
+ createTarget,
179
+ closeTarget,
180
+ activateTarget,
181
+ getTargetInfo,
182
+ getCachedTarget,
183
+ getCachedTargets,
184
+ cleanup
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Create a session registry for managing CDP sessions
190
+ * @param {import('../types.js').CDPConnection} connection - CDP connection
191
+ * @returns {Object} Session registry interface
192
+ */
193
+ export function createSessionRegistry(connection) {
194
+ const sessions = new Map();
195
+ const targetToSession = new Map();
196
+ const pendingAttach = new Map();
197
+ let boundHandlers = null;
198
+
199
+ function onAttached(params) {
200
+ const { sessionId, targetInfo } = params;
201
+ sessions.set(sessionId, { targetId: targetInfo.targetId, attached: true });
202
+ targetToSession.set(targetInfo.targetId, sessionId);
203
+ }
204
+
205
+ function onDetached(params) {
206
+ const { sessionId } = params;
207
+ const session = sessions.get(sessionId);
208
+ if (session) {
209
+ targetToSession.delete(session.targetId);
210
+ sessions.delete(sessionId);
211
+ }
212
+ }
213
+
214
+ function onTargetDestroyed(params) {
215
+ const { targetId } = params;
216
+ const sessionId = targetToSession.get(targetId);
217
+ if (sessionId) {
218
+ sessions.delete(sessionId);
219
+ targetToSession.delete(targetId);
220
+ }
221
+ }
222
+
223
+ // Setup handlers on creation
224
+ boundHandlers = { onAttached, onDetached, onTargetDestroyed };
225
+ connection.on('Target.attachedToTarget', boundHandlers.onAttached);
226
+ connection.on('Target.detachedFromTarget', boundHandlers.onDetached);
227
+ connection.on('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
228
+
229
+ async function doAttach(targetId) {
230
+ const result = await connection.send('Target.attachToTarget', {
231
+ targetId,
232
+ flatten: true
233
+ });
234
+
235
+ const sessionId = result.sessionId;
236
+ sessions.set(sessionId, { targetId, attached: true });
237
+ targetToSession.set(targetId, sessionId);
238
+
239
+ return sessionId;
240
+ }
241
+
242
+ /**
243
+ * Attach to a target
244
+ * @param {string} targetId - Target to attach to
245
+ * @returns {Promise<string>} Session ID
246
+ */
247
+ async function attach(targetId) {
248
+ const existing = targetToSession.get(targetId);
249
+ if (existing) return existing;
250
+
251
+ const pending = pendingAttach.get(targetId);
252
+ if (pending) return pending;
253
+
254
+ const attachPromise = doAttach(targetId);
255
+ pendingAttach.set(targetId, attachPromise);
256
+
257
+ try {
258
+ return await attachPromise;
259
+ } finally {
260
+ pendingAttach.delete(targetId);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Detach from a session
266
+ * @param {string} sessionId - Session to detach
267
+ * @returns {Promise<void>}
268
+ */
269
+ async function detach(sessionId) {
270
+ const session = sessions.get(sessionId);
271
+ if (!session) return;
272
+
273
+ await connection.send('Target.detachFromTarget', { sessionId });
274
+ sessions.delete(sessionId);
275
+ targetToSession.delete(session.targetId);
276
+ }
277
+
278
+ /**
279
+ * Detach from a target by target ID
280
+ * @param {string} targetId - Target to detach from
281
+ * @returns {Promise<void>}
282
+ */
283
+ async function detachByTarget(targetId) {
284
+ const sessionId = targetToSession.get(targetId);
285
+ if (sessionId) {
286
+ await detach(sessionId);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get session ID for a target
292
+ * @param {string} targetId - Target ID
293
+ * @returns {string|undefined} Session ID
294
+ */
295
+ function getSessionForTarget(targetId) {
296
+ return targetToSession.get(targetId);
297
+ }
298
+
299
+ /**
300
+ * Get target ID for a session
301
+ * @param {string} sessionId - Session ID
302
+ * @returns {string|undefined} Target ID
303
+ */
304
+ function getTargetForSession(sessionId) {
305
+ return sessions.get(sessionId)?.targetId;
306
+ }
307
+
308
+ /**
309
+ * Check if attached to a target
310
+ * @param {string} targetId - Target ID
311
+ * @returns {boolean}
312
+ */
313
+ function isAttached(targetId) {
314
+ return targetToSession.has(targetId);
315
+ }
316
+
317
+ /**
318
+ * Get all sessions
319
+ * @returns {Array<{sessionId: string, targetId: string}>}
320
+ */
321
+ function getAllSessions() {
322
+ return Array.from(sessions.entries()).map(([sessionId, data]) => ({
323
+ sessionId,
324
+ targetId: data.targetId
325
+ }));
326
+ }
327
+
328
+ /**
329
+ * Detach from all sessions
330
+ * @returns {Promise<void>}
331
+ */
332
+ async function detachAll() {
333
+ const sessionIds = Array.from(sessions.keys());
334
+ await Promise.all(sessionIds.map(s => detach(s)));
335
+ }
336
+
337
+ /**
338
+ * Clean up session registry
339
+ * @returns {Promise<void>}
340
+ */
341
+ async function cleanup() {
342
+ await detachAll();
343
+ if (boundHandlers) {
344
+ connection.off('Target.attachedToTarget', boundHandlers.onAttached);
345
+ connection.off('Target.detachedFromTarget', boundHandlers.onDetached);
346
+ connection.off('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
347
+ }
348
+ sessions.clear();
349
+ targetToSession.clear();
350
+ pendingAttach.clear();
351
+ }
352
+
353
+ return {
354
+ attach,
355
+ detach,
356
+ detachByTarget,
357
+ getSessionForTarget,
358
+ getTargetForSession,
359
+ isAttached,
360
+ getAllSessions,
361
+ detachAll,
362
+ cleanup
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Create a page session for CDP communication with a specific page
368
+ * @param {import('../types.js').CDPConnection} connection - CDP connection
369
+ * @param {string} sessionId - Session ID
370
+ * @param {string} targetId - Target ID
371
+ * @returns {import('../types.js').CDPSession} Page session interface
372
+ */
373
+ export function createPageSession(connection, sessionId, targetId) {
374
+ let valid = true;
375
+ let detachHandler = null;
376
+
377
+ function onDetached(params) {
378
+ if (params.sessionId === sessionId) {
379
+ valid = false;
380
+ if (detachHandler) {
381
+ connection.off('Target.detachedFromTarget', detachHandler);
382
+ }
383
+ }
384
+ }
385
+
386
+ detachHandler = onDetached;
387
+ connection.on('Target.detachedFromTarget', detachHandler);
388
+
389
+ /**
390
+ * Send CDP command via this session
391
+ * @param {string} method - CDP method name
392
+ * @param {Object} [params={}] - Command parameters
393
+ * @returns {Promise<Object>} Command result
394
+ */
395
+ async function send(method, params = {}) {
396
+ if (!valid) {
397
+ throw new Error(`Session ${sessionId} is no longer valid (target was closed or detached)`);
398
+ }
399
+ return connection.sendToSession(sessionId, method, params);
400
+ }
401
+
402
+ /**
403
+ * Subscribe to session-scoped event
404
+ * @param {string} event - Event name
405
+ * @param {function} callback - Event handler
406
+ */
407
+ function on(event, callback) {
408
+ connection.on(`${sessionId}:${event}`, callback);
409
+ }
410
+
411
+ /**
412
+ * Unsubscribe from session-scoped event
413
+ * @param {string} event - Event name
414
+ * @param {function} callback - Event handler
415
+ */
416
+ function off(event, callback) {
417
+ connection.off(`${sessionId}:${event}`, callback);
418
+ }
419
+
420
+ /**
421
+ * Dispose the session
422
+ */
423
+ function dispose() {
424
+ valid = false;
425
+ if (detachHandler) {
426
+ connection.off('Target.detachedFromTarget', detachHandler);
427
+ }
428
+ }
429
+
430
+ return {
431
+ send,
432
+ on,
433
+ off,
434
+ dispose,
435
+ isValid: () => valid,
436
+ get sessionId() { return sessionId; },
437
+ get targetId() { return targetId; }
438
+ };
439
+ }
package/src/cdp-skill.js CHANGED
@@ -11,13 +11,13 @@
11
11
  * node src/cdp-skill.js --debug '{"steps":[...]}' # Enable debug logging
12
12
  */
13
13
 
14
- import { createBrowser, getChromeStatus } from './cdp.js';
15
- import { createPageController } from './page.js';
16
- import { createElementLocator, createInputEmulator } from './dom.js';
17
- import { createScreenshotCapture, createConsoleCapture, createPdfCapture } from './capture.js';
14
+ import { createBrowser, getChromeStatus } from './cdp/index.js';
15
+ import { createPageController } from './page/index.js';
16
+ import { createElementLocator, createInputEmulator } from './dom/index.js';
17
+ import { createScreenshotCapture, createConsoleCapture, createPdfCapture } from './capture/index.js';
18
18
  import { createAriaSnapshot } from './aria.js';
19
- import { createCookieManager } from './page.js';
20
- import { runSteps } from './runner.js';
19
+ import { createCookieManager } from './page/index.js';
20
+ import { runSteps } from './runner/index.js';
21
21
  import fs from 'fs';
22
22
  import path from 'path';
23
23
  import os from 'os';
@@ -424,6 +424,7 @@ async function main() {
424
424
  const host = config.host || 'localhost';
425
425
  const port = config.port || 9222;
426
426
  const timeout = config.timeout || 30000;
427
+ const headless = config.headless || false; // Run Chrome in headless mode (no focus stealing)
427
428
  const tab = json.tab || config.tab; // Top-level tab takes precedence
428
429
 
429
430
  // Handle chromeStatus specially - no session needed
@@ -442,16 +443,29 @@ async function main() {
442
443
  process.exit(result.status === 'ok' ? 0 : 1);
443
444
  }
444
445
 
445
- // Connect to browser
446
+ // Connect to browser, auto-launch if needed
446
447
  browser = createBrowser({ host, port, connectTimeout: timeout });
447
448
 
448
449
  try {
449
450
  await browser.connect();
450
451
  } catch (err) {
451
- throw {
452
- type: ErrorType.CONNECTION,
453
- message: `Chrome not running on ${host}:${port} - ${err.message}`
454
- };
452
+ // Chrome not running - try to auto-launch
453
+ const status = await getChromeStatus({ host, port, autoLaunch: true, headless });
454
+ if (!status.running) {
455
+ throw {
456
+ type: ErrorType.CONNECTION,
457
+ message: `Chrome not running and failed to launch: ${status.error || 'unknown error'}`
458
+ };
459
+ }
460
+ // Retry connection after launch
461
+ try {
462
+ await browser.connect();
463
+ } catch (retryErr) {
464
+ throw {
465
+ type: ErrorType.CONNECTION,
466
+ message: `Chrome launched but connection failed: ${retryErr.message}`
467
+ };
468
+ }
455
469
  }
456
470
 
457
471
  // Get page session - requires explicit targetId or openTab step
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Constants
3
+ * Centralized configuration values used across the codebase
4
+ *
5
+ * EXPORTS:
6
+ * - TIMEOUTS - Timeout values in milliseconds
7
+ * - POLL_INTERVALS - Polling interval values
8
+ * - LIMITS - Various size and count limits
9
+ * - VALID_FORMATS - Allowed screenshot formats
10
+ * - KEY_DEFINITIONS - Special key code mappings
11
+ * - NON_EDITABLE_INPUT_TYPES - Input types that cannot be typed into
12
+ */
13
+
14
+ // Timeouts (milliseconds)
15
+ export const TIMEOUTS = {
16
+ MAX: 300000, // 5 minutes - absolute maximum
17
+ DEFAULT: 10000, // 10 seconds - default for actionability
18
+ NETWORK_IDLE: 500, // network idle threshold
19
+ STABILITY: 50, // DOM stability check
20
+ };
21
+
22
+ export const POLL_INTERVALS = {
23
+ DEFAULT: 100, // standard polling
24
+ FAST: 50, // actionability retries
25
+ };
26
+
27
+ export const LIMITS = {
28
+ MAX_CONSOLE_MESSAGES: 10000,
29
+ MAX_SCREENSHOT_DIMENSION: 16384,
30
+ MAX_PENDING_REQUESTS: 10000,
31
+ MAX_DOM_NODES: 500,
32
+ };
33
+
34
+ export const VALID_FORMATS = ['png', 'jpeg', 'webp'];
35
+
36
+ /**
37
+ * Special key definitions for keyboard input
38
+ */
39
+ export const KEY_DEFINITIONS = {
40
+ Enter: { key: 'Enter', code: 'Enter', keyCode: 13, text: '\r' },
41
+ Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
42
+ Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
43
+ Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
44
+ Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
45
+ Space: { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
46
+ ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
47
+ ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
48
+ ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
49
+ ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
50
+ Shift: { key: 'Shift', code: 'ShiftLeft', keyCode: 16 },
51
+ Control: { key: 'Control', code: 'ControlLeft', keyCode: 17 },
52
+ Alt: { key: 'Alt', code: 'AltLeft', keyCode: 18 },
53
+ Meta: { key: 'Meta', code: 'MetaLeft', keyCode: 91 },
54
+ F1: { key: 'F1', code: 'F1', keyCode: 112 },
55
+ F2: { key: 'F2', code: 'F2', keyCode: 113 },
56
+ F3: { key: 'F3', code: 'F3', keyCode: 114 },
57
+ F4: { key: 'F4', code: 'F4', keyCode: 115 },
58
+ F5: { key: 'F5', code: 'F5', keyCode: 116 },
59
+ F6: { key: 'F6', code: 'F6', keyCode: 117 },
60
+ F7: { key: 'F7', code: 'F7', keyCode: 118 },
61
+ F8: { key: 'F8', code: 'F8', keyCode: 119 },
62
+ F9: { key: 'F9', code: 'F9', keyCode: 120 },
63
+ F10: { key: 'F10', code: 'F10', keyCode: 121 },
64
+ F11: { key: 'F11', code: 'F11', keyCode: 122 },
65
+ F12: { key: 'F12', code: 'F12', keyCode: 123 },
66
+ Home: { key: 'Home', code: 'Home', keyCode: 36 },
67
+ End: { key: 'End', code: 'End', keyCode: 35 },
68
+ PageUp: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
69
+ PageDown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
70
+ Insert: { key: 'Insert', code: 'Insert', keyCode: 45 }
71
+ };
72
+
73
+ /**
74
+ * Non-editable input types that cannot receive text input
75
+ */
76
+ export const NON_EDITABLE_INPUT_TYPES = [
77
+ 'button', 'checkbox', 'color', 'file', 'hidden',
78
+ 'image', 'radio', 'range', 'reset', 'submit'
79
+ ];