Skip to content

Commit 62449c9

Browse files
committed
Add capability for KeyboardEvent.code + capture
This: - Adds the capability for KeycodeLayout to also use the `code` field from KeyboardEvents. - Adds a map for `code` fields for EN_US. - Adds a package with a widget for capturing keyboard layouts. - Adds an example app for capturing keyboard layouts. TODOs: - Add tests for capture widget / example. - Figure out a pattern for adding other layouts. Should they be added to keyboard package, or to one or more separate language packs?
1 parent 3e76476 commit 62449c9

File tree

23 files changed

+1313
-19
lines changed

23 files changed

+1313
-19
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!--
2+
~ Copyright (c) Jupyter Development Team.
3+
~ Distributed under the terms of the Modified BSD License.
4+
-->
5+
6+
<!DOCTYPE html>
7+
<html>
8+
<head>
9+
<meta charset="UTF-8">
10+
<script type="text/javascript" src="build/bundle.example.js"></script>
11+
</head>
12+
<body>
13+
</body>
14+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@lumino/example-keyboard-capture",
3+
"version": "2.0.0",
4+
"private": true,
5+
"scripts": {
6+
"build": "tsc && rollup -c",
7+
"clean": "rimraf build"
8+
},
9+
"dependencies": {
10+
"@lumino/keyboard-capture": "^2.0.0",
11+
"@lumino/signaling": "^2.1.3",
12+
"@lumino/widgets": "^2.6.0"
13+
},
14+
"devDependencies": {
15+
"@lumino/messaging": "^2.0.2",
16+
"@rollup/plugin-node-resolve": "^15.0.1",
17+
"rimraf": "^5.0.1",
18+
"rollup": "^3.25.1",
19+
"rollup-plugin-styles": "^4.0.0",
20+
"typescript": "~5.1.3"
21+
}
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
import { createRollupExampleConfig } from '@lumino/buildutils';
7+
const rollupConfig = createRollupExampleConfig();
8+
export default rollupConfig;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
/*-----------------------------------------------------------------------------
4+
| Copyright (c) 2019, PhosphorJS Contributors
5+
|
6+
| Distributed under the terms of the BSD 3-Clause License.
7+
|
8+
| The full license is in the file LICENSE, distributed with this software.
9+
|----------------------------------------------------------------------------*/
10+
11+
import { CaptureWidget } from '@lumino/keyboard-capture';
12+
import { Message } from '@lumino/messaging';
13+
import { ISignal, Signal } from '@lumino/signaling';
14+
import { Panel, Widget } from '@lumino/widgets';
15+
16+
import '../style/index.css';
17+
18+
export class OutputWidget extends Widget {
19+
/**
20+
*
21+
*/
22+
constructor(options?: Widget.IOptions) {
23+
super(options);
24+
this._output = document.createElement('div');
25+
this._exportButton = document.createElement('button');
26+
this._exportButton.innerText = 'Show';
27+
this._copyButton = document.createElement('button');
28+
this._copyButton.innerText = 'Copy';
29+
this._clearButton = document.createElement('button');
30+
this._clearButton.innerText = 'Clear';
31+
this.node.appendChild(this._exportButton);
32+
this.node.appendChild(this._copyButton);
33+
this.node.appendChild(this._clearButton);
34+
this.node.appendChild(this._output);
35+
this.addClass('lm-keyboardCaptureOutputArea');
36+
}
37+
38+
set value(content: string) {
39+
this._output.innerHTML = content;
40+
}
41+
42+
get action(): ISignal<this, 'display' | 'clipboard' | 'clear'> {
43+
return this._action;
44+
}
45+
46+
/**
47+
* Handle the DOM events for the widget.
48+
*
49+
* @param event - The DOM event sent to the element.
50+
*/
51+
handleEvent(event: Event): void {
52+
switch (event.type) {
53+
case 'click':
54+
if (event.target === this._exportButton) {
55+
event.stopPropagation();
56+
this._action.emit('display');
57+
} else if (event.target === this._copyButton) {
58+
event.stopPropagation();
59+
this._action.emit('clipboard');
60+
} else if (event.target === this._clearButton) {
61+
event.stopPropagation();
62+
this._action.emit('clear');
63+
}
64+
break;
65+
}
66+
}
67+
68+
/**
69+
* A message handler invoked on a `'before-attach'` message.
70+
*/
71+
protected onBeforeAttach(msg: Message): void {
72+
this._exportButton.addEventListener('click', this);
73+
this._copyButton.addEventListener('click', this);
74+
this._clearButton.addEventListener('click', this);
75+
super.onBeforeAttach(msg);
76+
}
77+
78+
/**
79+
* A message handler invoked on an `'after-detach'` message.
80+
*/
81+
protected onAfterDetach(msg: Message): void {
82+
super.onAfterDetach(msg);
83+
this._exportButton.removeEventListener('click', this);
84+
this._copyButton.removeEventListener('click', this);
85+
this._clearButton.removeEventListener('click', this);
86+
}
87+
88+
private _output: HTMLElement;
89+
private _exportButton: HTMLButtonElement;
90+
private _copyButton: HTMLButtonElement;
91+
private _clearButton: HTMLButtonElement;
92+
private _action = new Signal<this, 'display' | 'clipboard' | 'clear'>(this);
93+
}
94+
95+
/**
96+
* Initialize the applicaiton.
97+
*/
98+
async function init(): Promise<void> {
99+
// Add the text editors to a dock panel.
100+
let capture = new CaptureWidget();
101+
let output = new OutputWidget();
102+
103+
capture.node.textContent =
104+
'Focus me and hit each key on your keyboard without any modifiers';
105+
106+
// Add the dock panel to the document.
107+
let box = new Panel();
108+
box.id = 'main';
109+
box.addWidget(capture);
110+
box.addWidget(output);
111+
112+
capture.dataAdded.connect((sender, entry) => {
113+
output.value = `Added ${entry.type}: ${
114+
entry.code ? `${entry.code} →` : ''
115+
} <kbd>${entry.key}</kbd>`;
116+
});
117+
output.action.connect((sender, action) => {
118+
if (action === 'clipboard') {
119+
navigator.clipboard.writeText(capture.formatMap());
120+
} else if (action === 'clear') {
121+
capture.clear();
122+
output.value = ' ';
123+
} else {
124+
output.value = `<pre>${capture.formatMap()}</pre>`;
125+
}
126+
});
127+
128+
window.onresize = () => {
129+
box.update();
130+
};
131+
Widget.attach(box, document.body);
132+
}
133+
134+
window.onload = init;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*-----------------------------------------------------------------------------
2+
| Copyright (c) Jupyter Development Team.
3+
| Copyright (c) 2014-2017, PhosphorJS Contributors
4+
|
5+
| Distributed under the terms of the BSD 3-Clause License.
6+
|
7+
| The full license is in the file LICENSE, distributed with this software.
8+
|----------------------------------------------------------------------------*/
9+
@import '~@lumino/widgets/style/index.css';
10+
11+
body {
12+
display: flex;
13+
flex-direction: column;
14+
position: absolute;
15+
top: 0;
16+
left: 0;
17+
right: 0;
18+
bottom: 0;
19+
margin: 0;
20+
padding: 0;
21+
}
22+
23+
#main {
24+
flex: 1 1 auto;
25+
overflow: auto;
26+
padding: 10px;
27+
}
28+
29+
.lm-keyboardCaptureArea {
30+
border-radius: 5px;
31+
border: 3px dashed #88a;
32+
padding: 6px;
33+
margin: 6px;
34+
}
35+
36+
.lm-keyboardCaptureOutputArea kbd {
37+
background-color: #eee;
38+
border-radius: 5px;
39+
border: 3px solid #b4b4b4;
40+
box-shadow:
41+
0 1px 1px rgba(0, 0, 0, 0.2),
42+
0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
43+
color: #333;
44+
display: inline-block;
45+
font-size: 0.85em;
46+
font-weight: 700;
47+
line-height: 1;
48+
padding: 2px 4px;
49+
white-space: nowrap;
50+
}
51+
52+
.lm-keyboardCaptureOutputArea button {
53+
margin: 4px;
54+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"declaration": false,
4+
"noImplicitAny": true,
5+
"noEmitOnError": true,
6+
"noUnusedLocals": true,
7+
"strictNullChecks": true,
8+
"sourceMap": true,
9+
"module": "ES6",
10+
"moduleResolution": "node",
11+
"target": "ES2018",
12+
"outDir": "./build",
13+
"lib": ["DOM", "ES2018"],
14+
"types": []
15+
},
16+
"include": ["src/*"]
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
3+
*/
4+
{
5+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
6+
7+
/**
8+
* Optionally specifies another JSON config file that this file extends from. This provides a way for
9+
* standard settings to be shared across multiple projects.
10+
*
11+
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
12+
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
13+
* resolved using NodeJS require().
14+
*
15+
* SUPPORTED TOKENS: none
16+
* DEFAULT VALUE: ""
17+
*/
18+
"extends": "../../api-extractor-base.json"
19+
// "extends": "my-package/include/api-extractor-base.json"
20+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"name": "@lumino/keyboard-capture",
3+
"version": "2.0.0",
4+
"description": "Lumino Keyboard Capture widget",
5+
"homepage": "https://github.com/jupyterlab/lumino",
6+
"bugs": {
7+
"url": "https://github.com/jupyterlab/lumino/issues"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/jupyterlab/lumino.git"
12+
},
13+
"license": "BSD-3-Clause",
14+
"author": "Project Jupyter",
15+
"main": "dist/index.js",
16+
"jsdelivr": "dist/index.min.js",
17+
"unpkg": "dist/index.min.js",
18+
"module": "dist/index.es6",
19+
"types": "types/index.d.ts",
20+
"files": [
21+
"dist/*",
22+
"src/*",
23+
"types/*"
24+
],
25+
"scripts": {
26+
"api": "api-extractor run --local --verbose",
27+
"build": "yarn build:src && rollup -c",
28+
"build:src": "tsc --build",
29+
"build:test": "yarn clean:test && tsc --build tests && cd tests && rollup -c",
30+
"clean": "rimraf ./lib *.tsbuildinfo ./types ./dist",
31+
"clean:test": "rimraf tests/lib tests/tsconfig.tsbuildinfo",
32+
"minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js",
33+
"test": "yarn build:test && web-test-runner tests/lib/bundle.test.js --node-resolve --playwright",
34+
"test:chromium": "yarn test -- --browsers chromium",
35+
"test:debug": "yarn test -- --manual --open",
36+
"test:firefox": "yarn test -- --browsers firefox",
37+
"test:webkit": "yarn test -- --browsers webkit",
38+
"watch": "tsc --build --watch"
39+
},
40+
"dependencies": {
41+
"@lumino/keyboard": "^2.0.2",
42+
"@lumino/signaling": "^2.1.3",
43+
"@lumino/widgets": "^2.6.0"
44+
},
45+
"devDependencies": {
46+
"@lumino/messaging": "^2.0.2",
47+
"@microsoft/api-extractor": "^7.36.0",
48+
"@rollup/plugin-commonjs": "^24.0.0",
49+
"@rollup/plugin-node-resolve": "^15.0.1",
50+
"@types/chai": "^3.4.35",
51+
"@types/mocha": "^2.2.39",
52+
"@web/test-runner": "^0.18.2",
53+
"@web/test-runner-playwright": "^0.11.0",
54+
"chai": "^4.3.4",
55+
"mocha": "^9.0.3",
56+
"rimraf": "^5.0.1",
57+
"rollup": "^3.25.1",
58+
"rollup-plugin-postcss": "^4.0.2",
59+
"rollup-plugin-sourcemaps": "^0.6.3",
60+
"terser": "^5.18.1",
61+
"tslib": "^2.3.0",
62+
"typescript": "~5.1.3"
63+
},
64+
"publishConfig": {
65+
"access": "public"
66+
}
67+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
import { createRollupConfig } from '@lumino/buildutils';
7+
import * as fs from 'fs';
8+
9+
const config = createRollupConfig({
10+
pkg: JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
11+
});
12+
13+
export default config;

0 commit comments

Comments
 (0)