@dxos/keyboard 0.3.10-main.dcfebef → 0.3.10-main.f460bb8
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.
- package/dist/lib/browser/index.mjs +27 -21
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +27 -21
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/keyboard.d.ts +4 -2
- package/dist/types/src/keyboard.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/keyboard.stories.tsx +1 -1
- package/src/keyboard.ts +33 -27
|
@@ -10,6 +10,12 @@ import {
|
|
|
10
10
|
// packages/common/keyboard/src/keyboard.ts
|
|
11
11
|
import { invariant } from "@dxos/invariant";
|
|
12
12
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/keyboard/src/keyboard.ts";
|
|
13
|
+
var modifiers = [
|
|
14
|
+
"alt",
|
|
15
|
+
"ctrl",
|
|
16
|
+
"shift",
|
|
17
|
+
"meta"
|
|
18
|
+
];
|
|
13
19
|
var KeyboardContext = class {
|
|
14
20
|
constructor() {
|
|
15
21
|
this._keyMap = /* @__PURE__ */ new Map();
|
|
@@ -23,15 +29,10 @@ var KeyboardContext = class {
|
|
|
23
29
|
bind(config) {
|
|
24
30
|
const { binding } = config;
|
|
25
31
|
const parts = binding.split("+");
|
|
26
|
-
const mods =
|
|
27
|
-
"alt",
|
|
28
|
-
"ctrl",
|
|
29
|
-
"meta",
|
|
30
|
-
"shift"
|
|
31
|
-
].includes(key)).sort();
|
|
32
|
+
const mods = modifiers.filter((key) => parts.includes(key));
|
|
32
33
|
invariant(mods.length === 0 || mods.length === parts.length - 1, void 0, {
|
|
33
34
|
F: __dxlog_file,
|
|
34
|
-
L:
|
|
35
|
+
L: 40,
|
|
35
36
|
S: this,
|
|
36
37
|
A: [
|
|
37
38
|
"mods.length === 0 || mods.length === parts.length - 1",
|
|
@@ -77,9 +78,22 @@ var Keyboard = class _Keyboard {
|
|
|
77
78
|
destroy() {
|
|
78
79
|
document.removeEventListener("keydown", this._keyHandler);
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
setCurrentContext(path = ROOT) {
|
|
81
82
|
this._path = path;
|
|
82
83
|
}
|
|
84
|
+
getCurrentContext() {
|
|
85
|
+
return this._path;
|
|
86
|
+
}
|
|
87
|
+
getContext(path = ROOT) {
|
|
88
|
+
let context = this._keyMap.get(path);
|
|
89
|
+
if (!context) {
|
|
90
|
+
context = new KeyboardContext();
|
|
91
|
+
this._keyMap.set(path, context);
|
|
92
|
+
this._contexts.push(path);
|
|
93
|
+
this._contexts.sort();
|
|
94
|
+
}
|
|
95
|
+
return context;
|
|
96
|
+
}
|
|
83
97
|
getBindings() {
|
|
84
98
|
const bindings = /* @__PURE__ */ new Map();
|
|
85
99
|
for (let i = 0; i < this._contexts.length; ++i) {
|
|
@@ -92,31 +106,23 @@ var Keyboard = class _Keyboard {
|
|
|
92
106
|
}
|
|
93
107
|
return Array.from(bindings.values());
|
|
94
108
|
}
|
|
95
|
-
getContext(path = ROOT) {
|
|
96
|
-
let context = this._keyMap.get(path);
|
|
97
|
-
if (!context) {
|
|
98
|
-
context = new KeyboardContext();
|
|
99
|
-
this._keyMap.set(path, context);
|
|
100
|
-
this._contexts.push(path);
|
|
101
|
-
this._contexts.sort();
|
|
102
|
-
}
|
|
103
|
-
return context;
|
|
104
|
-
}
|
|
105
109
|
handleKeyDown(event) {
|
|
106
110
|
const { altKey, ctrlKey, metaKey, shiftKey, key } = event;
|
|
107
111
|
if (key !== "Alt" && key !== "Control" && key !== "Meta" && key !== "Shift") {
|
|
112
|
+
const tagName = event.target?.tagName;
|
|
113
|
+
const isInput = tagName === "INPUT" || tagName === "TEXTAREA" || event.target?.getAttribute("contenteditable");
|
|
108
114
|
const str = [
|
|
109
115
|
altKey && "alt",
|
|
110
116
|
ctrlKey && "ctrl",
|
|
111
|
-
metaKey && "meta",
|
|
112
117
|
shiftKey && "shift",
|
|
118
|
+
metaKey && "meta",
|
|
113
119
|
key
|
|
114
120
|
].filter(Boolean).join("+");
|
|
115
121
|
for (let i = this._contexts.length - 1; i >= 0; --i) {
|
|
116
122
|
const path = this._contexts[i];
|
|
117
123
|
if (this._path.startsWith(path)) {
|
|
118
|
-
const { data, handler } = this.getContext(path).get(str) ?? {};
|
|
119
|
-
if (handler) {
|
|
124
|
+
const { data, handler, disableInput } = this.getContext(path).get(str) ?? {};
|
|
125
|
+
if (handler && (!isInput || !disableInput)) {
|
|
120
126
|
const result = handler({
|
|
121
127
|
context: path,
|
|
122
128
|
binding: str,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/keyboard.ts", "../../../src/util.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n binding: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n binding: string;\n handler: KeyHandler;\n data?: any;\n};\n\nclass KeyboardContext {\n readonly _keyMap = new Map<string, KeyBinding>();\n\n get bindings() {\n return Array.from(this._keyMap.values());\n }\n\n get(binding: string) {\n return this._keyMap.get(binding);\n }\n\n bind(config: KeyBinding) {\n // Normalize order of modifiers.\n const { binding } = config;\n const parts = binding.split('+');\n const mods =
|
|
5
|
-
"mappings": ";;;;;;;;;;AAIA,SAASA,iBAAiB;;
|
|
6
|
-
"names": ["invariant", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n binding: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n binding: string;\n handler: KeyHandler;\n disableInput?: boolean;\n data?: any;\n};\n\n// Keybinding is normalized to this order.\nconst modifiers = ['alt', 'ctrl', 'shift', 'meta'];\n\nclass KeyboardContext {\n readonly _keyMap = new Map<string, KeyBinding>();\n\n get bindings() {\n return Array.from(this._keyMap.values());\n }\n\n get(binding: string) {\n return this._keyMap.get(binding);\n }\n\n bind(config: KeyBinding) {\n // Normalize order of modifiers.\n const { binding } = config;\n const parts = binding.split('+');\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n if (mods.length) {\n config.binding = [...mods, parts[parts.length - 1]].join('+');\n }\n\n this._keyMap.set(config.binding, config);\n }\n\n unbind(binding: string) {\n this._keyMap.delete(binding);\n }\n}\n\nconst ROOT = '';\n\n/**\n * Manages context-aware key bindings.\n */\nexport class Keyboard {\n static singleton = new Keyboard();\n\n private readonly _root = new KeyboardContext();\n private readonly _keyMap = new Map<string, KeyboardContext>([[ROOT, this._root]]);\n private readonly _contexts: string[] = [ROOT];\n private readonly _keyHandler = this.handleKeyDown.bind(this);\n private _path = ROOT;\n\n initialize() {\n document.addEventListener('keydown', this._keyHandler);\n }\n\n destroy() {\n document.removeEventListener('keydown', this._keyHandler);\n }\n\n bind = this._root.bind.bind(this._root);\n unbind = this._root.unbind.bind(this._root);\n\n setCurrentContext(path = ROOT) {\n this._path = path;\n }\n\n getCurrentContext() {\n return this._path;\n }\n\n getContext(path = ROOT): KeyboardContext {\n let context = this._keyMap.get(path);\n if (!context) {\n context = new KeyboardContext();\n this._keyMap.set(path, context);\n this._contexts.push(path);\n this._contexts.sort();\n }\n\n return context;\n }\n\n getBindings() {\n const bindings = new Map<string, KeyBinding>();\n for (let i = 0; i < this._contexts.length; ++i) {\n const path = this._contexts[i];\n if (this._path.startsWith(path)) {\n this.getContext(path).bindings.forEach((binding) => {\n bindings.set(binding.binding, binding);\n });\n }\n }\n\n return Array.from(bindings.values());\n }\n\n handleKeyDown(event: KeyboardEvent) {\n const { altKey, ctrlKey, metaKey, shiftKey, key } = event;\n\n if (key !== 'Alt' && key !== 'Control' && key !== 'Meta' && key !== 'Shift') {\n // Binding option to check for input or contenteditable.\n const tagName = (event.target as any)?.tagName;\n const isInput =\n tagName === 'INPUT' || tagName === 'TEXTAREA' || (event.target as any)?.getAttribute('contenteditable');\n\n // Normalized key binding.\n const str = [altKey && 'alt', ctrlKey && 'ctrl', shiftKey && 'shift', metaKey && 'meta', key]\n .filter(Boolean)\n .join('+');\n\n // Scan matching contexts.\n for (let i = this._contexts.length - 1; i >= 0; --i) {\n const path = this._contexts[i];\n if (this._path.startsWith(path)) {\n const { data, handler, disableInput } = this.getContext(path).get(str) ?? {};\n if (handler && (!isInput || !disableInput)) {\n const result = handler({ context: path, binding: str, data, event });\n if (result !== false) {\n // TODO(burdon): This doesn't prevent actions in markdown editor.\n // Reference: https://codemirror.net/docs/ref/#view.KeyBinding\n event.preventDefault();\n event.stopPropagation();\n }\n\n return;\n }\n }\n }\n }\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\n// https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents\n// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n// https://support.apple.com/en-us/HT201236\n// https://support.apple.com/guide/mac-help/what-are-those-symbols-shown-in-menus-cpmh0011/mac\n\n// TODO(burdon): Show Mac/Windows/Linux variants.\nconst symbols: Record<string, string> = {\n // Mods.\n ctrl: '⌃',\n shift: '⇧',\n alt: '⌥',\n meta: '⌘',\n\n // Special keys.\n Backspace: '⌫',\n Enter: '⏎',\n Escape: '⎋',\n Space: '␣',\n Tab: '⇥',\n};\n\nexport const keySymbols = (keyBinding: string): string[] => {\n const parts = keyBinding.split('+');\n return parts.map((part) => symbols[part] ?? part.toUpperCase());\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["invariant", "modifiers", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "length", "join", "set", "unbind", "delete", "ROOT", "Keyboard", "_root", "_contexts", "_keyHandler", "handleKeyDown", "_path", "singleton", "initialize", "document", "addEventListener", "destroy", "removeEventListener", "setCurrentContext", "path", "getCurrentContext", "getContext", "context", "push", "sort", "getBindings", "i", "startsWith", "forEach", "event", "altKey", "ctrlKey", "metaKey", "shiftKey", "tagName", "target", "isInput", "getAttribute", "str", "Boolean", "data", "handler", "disableInput", "result", "preventDefault", "stopPropagation", "symbols", "ctrl", "shift", "alt", "meta", "Backspace", "Enter", "Escape", "Space", "Tab", "keySymbols", "keyBinding", "parts", "split", "map", "part", "toUpperCase"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"inject-globals:@inject-globals":{"bytes":384,"imports":[{"path":"@dxos/node-std/inject-globals","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/keyboard.ts":{"bytes":
|
|
1
|
+
{"inputs":{"inject-globals:@inject-globals":{"bytes":384,"imports":[{"path":"@dxos/node-std/inject-globals","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/keyboard.ts":{"bytes":15101,"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@inject-globals","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/util.ts":{"bytes":2733,"imports":[{"path":"@inject-globals","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/index.ts":{"bytes":544,"imports":[{"path":"packages/common/keyboard/src/keyboard.ts","kind":"import-statement","original":"./keyboard"},{"path":"packages/common/keyboard/src/util.ts","kind":"import-statement","original":"./util"},{"path":"@inject-globals","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"packages/common/keyboard/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":8722},"packages/common/keyboard/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/inject-globals","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true}],"exports":["Keyboard","keySymbols"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"inject-globals:@inject-globals":{"bytesInOutput":79},"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3582},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":4249}}}
|
package/dist/lib/node/index.cjs
CHANGED
|
@@ -24,6 +24,12 @@ __export(node_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(node_exports);
|
|
25
25
|
var import_invariant = require("@dxos/invariant");
|
|
26
26
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/keyboard/src/keyboard.ts";
|
|
27
|
+
var modifiers = [
|
|
28
|
+
"alt",
|
|
29
|
+
"ctrl",
|
|
30
|
+
"shift",
|
|
31
|
+
"meta"
|
|
32
|
+
];
|
|
27
33
|
var KeyboardContext = class {
|
|
28
34
|
constructor() {
|
|
29
35
|
this._keyMap = /* @__PURE__ */ new Map();
|
|
@@ -37,15 +43,10 @@ var KeyboardContext = class {
|
|
|
37
43
|
bind(config) {
|
|
38
44
|
const { binding } = config;
|
|
39
45
|
const parts = binding.split("+");
|
|
40
|
-
const mods =
|
|
41
|
-
"alt",
|
|
42
|
-
"ctrl",
|
|
43
|
-
"meta",
|
|
44
|
-
"shift"
|
|
45
|
-
].includes(key)).sort();
|
|
46
|
+
const mods = modifiers.filter((key) => parts.includes(key));
|
|
46
47
|
(0, import_invariant.invariant)(mods.length === 0 || mods.length === parts.length - 1, void 0, {
|
|
47
48
|
F: __dxlog_file,
|
|
48
|
-
L:
|
|
49
|
+
L: 40,
|
|
49
50
|
S: this,
|
|
50
51
|
A: [
|
|
51
52
|
"mods.length === 0 || mods.length === parts.length - 1",
|
|
@@ -91,9 +92,22 @@ var Keyboard = class _Keyboard {
|
|
|
91
92
|
destroy() {
|
|
92
93
|
document.removeEventListener("keydown", this._keyHandler);
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
setCurrentContext(path = ROOT) {
|
|
95
96
|
this._path = path;
|
|
96
97
|
}
|
|
98
|
+
getCurrentContext() {
|
|
99
|
+
return this._path;
|
|
100
|
+
}
|
|
101
|
+
getContext(path = ROOT) {
|
|
102
|
+
let context = this._keyMap.get(path);
|
|
103
|
+
if (!context) {
|
|
104
|
+
context = new KeyboardContext();
|
|
105
|
+
this._keyMap.set(path, context);
|
|
106
|
+
this._contexts.push(path);
|
|
107
|
+
this._contexts.sort();
|
|
108
|
+
}
|
|
109
|
+
return context;
|
|
110
|
+
}
|
|
97
111
|
getBindings() {
|
|
98
112
|
const bindings = /* @__PURE__ */ new Map();
|
|
99
113
|
for (let i = 0; i < this._contexts.length; ++i) {
|
|
@@ -106,31 +120,23 @@ var Keyboard = class _Keyboard {
|
|
|
106
120
|
}
|
|
107
121
|
return Array.from(bindings.values());
|
|
108
122
|
}
|
|
109
|
-
getContext(path = ROOT) {
|
|
110
|
-
let context = this._keyMap.get(path);
|
|
111
|
-
if (!context) {
|
|
112
|
-
context = new KeyboardContext();
|
|
113
|
-
this._keyMap.set(path, context);
|
|
114
|
-
this._contexts.push(path);
|
|
115
|
-
this._contexts.sort();
|
|
116
|
-
}
|
|
117
|
-
return context;
|
|
118
|
-
}
|
|
119
123
|
handleKeyDown(event) {
|
|
120
124
|
const { altKey, ctrlKey, metaKey, shiftKey, key } = event;
|
|
121
125
|
if (key !== "Alt" && key !== "Control" && key !== "Meta" && key !== "Shift") {
|
|
126
|
+
const tagName = event.target?.tagName;
|
|
127
|
+
const isInput = tagName === "INPUT" || tagName === "TEXTAREA" || event.target?.getAttribute("contenteditable");
|
|
122
128
|
const str = [
|
|
123
129
|
altKey && "alt",
|
|
124
130
|
ctrlKey && "ctrl",
|
|
125
|
-
metaKey && "meta",
|
|
126
131
|
shiftKey && "shift",
|
|
132
|
+
metaKey && "meta",
|
|
127
133
|
key
|
|
128
134
|
].filter(Boolean).join("+");
|
|
129
135
|
for (let i = this._contexts.length - 1; i >= 0; --i) {
|
|
130
136
|
const path = this._contexts[i];
|
|
131
137
|
if (this._path.startsWith(path)) {
|
|
132
|
-
const { data, handler } = this.getContext(path).get(str) ?? {};
|
|
133
|
-
if (handler) {
|
|
138
|
+
const { data, handler, disableInput } = this.getContext(path).get(str) ?? {};
|
|
139
|
+
if (handler && (!isInput || !disableInput)) {
|
|
134
140
|
const result = handler({
|
|
135
141
|
context: path,
|
|
136
142
|
binding: str,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/keyboard.ts", "../../../src/util.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n binding: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n binding: string;\n handler: KeyHandler;\n data?: any;\n};\n\nclass KeyboardContext {\n readonly _keyMap = new Map<string, KeyBinding>();\n\n get bindings() {\n return Array.from(this._keyMap.values());\n }\n\n get(binding: string) {\n return this._keyMap.get(binding);\n }\n\n bind(config: KeyBinding) {\n // Normalize order of modifiers.\n const { binding } = config;\n const parts = binding.split('+');\n const mods =
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAIA,uBAA0B;;
|
|
6
|
-
"names": ["KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n binding: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n binding: string;\n handler: KeyHandler;\n disableInput?: boolean;\n data?: any;\n};\n\n// Keybinding is normalized to this order.\nconst modifiers = ['alt', 'ctrl', 'shift', 'meta'];\n\nclass KeyboardContext {\n readonly _keyMap = new Map<string, KeyBinding>();\n\n get bindings() {\n return Array.from(this._keyMap.values());\n }\n\n get(binding: string) {\n return this._keyMap.get(binding);\n }\n\n bind(config: KeyBinding) {\n // Normalize order of modifiers.\n const { binding } = config;\n const parts = binding.split('+');\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n if (mods.length) {\n config.binding = [...mods, parts[parts.length - 1]].join('+');\n }\n\n this._keyMap.set(config.binding, config);\n }\n\n unbind(binding: string) {\n this._keyMap.delete(binding);\n }\n}\n\nconst ROOT = '';\n\n/**\n * Manages context-aware key bindings.\n */\nexport class Keyboard {\n static singleton = new Keyboard();\n\n private readonly _root = new KeyboardContext();\n private readonly _keyMap = new Map<string, KeyboardContext>([[ROOT, this._root]]);\n private readonly _contexts: string[] = [ROOT];\n private readonly _keyHandler = this.handleKeyDown.bind(this);\n private _path = ROOT;\n\n initialize() {\n document.addEventListener('keydown', this._keyHandler);\n }\n\n destroy() {\n document.removeEventListener('keydown', this._keyHandler);\n }\n\n bind = this._root.bind.bind(this._root);\n unbind = this._root.unbind.bind(this._root);\n\n setCurrentContext(path = ROOT) {\n this._path = path;\n }\n\n getCurrentContext() {\n return this._path;\n }\n\n getContext(path = ROOT): KeyboardContext {\n let context = this._keyMap.get(path);\n if (!context) {\n context = new KeyboardContext();\n this._keyMap.set(path, context);\n this._contexts.push(path);\n this._contexts.sort();\n }\n\n return context;\n }\n\n getBindings() {\n const bindings = new Map<string, KeyBinding>();\n for (let i = 0; i < this._contexts.length; ++i) {\n const path = this._contexts[i];\n if (this._path.startsWith(path)) {\n this.getContext(path).bindings.forEach((binding) => {\n bindings.set(binding.binding, binding);\n });\n }\n }\n\n return Array.from(bindings.values());\n }\n\n handleKeyDown(event: KeyboardEvent) {\n const { altKey, ctrlKey, metaKey, shiftKey, key } = event;\n\n if (key !== 'Alt' && key !== 'Control' && key !== 'Meta' && key !== 'Shift') {\n // Binding option to check for input or contenteditable.\n const tagName = (event.target as any)?.tagName;\n const isInput =\n tagName === 'INPUT' || tagName === 'TEXTAREA' || (event.target as any)?.getAttribute('contenteditable');\n\n // Normalized key binding.\n const str = [altKey && 'alt', ctrlKey && 'ctrl', shiftKey && 'shift', metaKey && 'meta', key]\n .filter(Boolean)\n .join('+');\n\n // Scan matching contexts.\n for (let i = this._contexts.length - 1; i >= 0; --i) {\n const path = this._contexts[i];\n if (this._path.startsWith(path)) {\n const { data, handler, disableInput } = this.getContext(path).get(str) ?? {};\n if (handler && (!isInput || !disableInput)) {\n const result = handler({ context: path, binding: str, data, event });\n if (result !== false) {\n // TODO(burdon): This doesn't prevent actions in markdown editor.\n // Reference: https://codemirror.net/docs/ref/#view.KeyBinding\n event.preventDefault();\n event.stopPropagation();\n }\n\n return;\n }\n }\n }\n }\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\n// https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents\n// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n// https://support.apple.com/en-us/HT201236\n// https://support.apple.com/guide/mac-help/what-are-those-symbols-shown-in-menus-cpmh0011/mac\n\n// TODO(burdon): Show Mac/Windows/Linux variants.\nconst symbols: Record<string, string> = {\n // Mods.\n ctrl: '⌃',\n shift: '⇧',\n alt: '⌥',\n meta: '⌘',\n\n // Special keys.\n Backspace: '⌫',\n Enter: '⏎',\n Escape: '⎋',\n Space: '␣',\n Tab: '⇥',\n};\n\nexport const keySymbols = (keyBinding: string): string[] => {\n const parts = keyBinding.split('+');\n return parts.map((part) => symbols[part] ?? part.toUpperCase());\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["modifiers", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "invariant", "length", "join", "set", "unbind", "delete", "ROOT", "Keyboard", "_root", "_contexts", "_keyHandler", "handleKeyDown", "_path", "singleton", "initialize", "document", "addEventListener", "destroy", "removeEventListener", "setCurrentContext", "path", "getCurrentContext", "getContext", "context", "push", "sort", "getBindings", "i", "startsWith", "forEach", "event", "altKey", "ctrlKey", "metaKey", "shiftKey", "tagName", "target", "isInput", "getAttribute", "str", "Boolean", "data", "handler", "disableInput", "result", "preventDefault", "stopPropagation", "symbols", "ctrl", "shift", "alt", "meta", "Backspace", "Enter", "Escape", "Space", "Tab", "keySymbols", "keyBinding", "map", "part", "toUpperCase"]
|
|
7
7
|
}
|
package/dist/lib/node/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytes":15101,"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/util.ts":{"bytes":2733,"imports":[],"format":"esm"},"packages/common/keyboard/src/index.ts":{"bytes":544,"imports":[{"path":"packages/common/keyboard/src/keyboard.ts","kind":"import-statement","original":"./keyboard"},{"path":"packages/common/keyboard/src/util.ts","kind":"import-statement","original":"./util"}],"format":"esm"}},"outputs":{"packages/common/keyboard/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":8713},"packages/common/keyboard/dist/lib/node/index.cjs":{"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true}],"exports":["Keyboard","keySymbols"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3582},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":4101}}}
|
|
@@ -7,6 +7,7 @@ export type KeyHandler = (props: {
|
|
|
7
7
|
export type KeyBinding = {
|
|
8
8
|
binding: string;
|
|
9
9
|
handler: KeyHandler;
|
|
10
|
+
disableInput?: boolean;
|
|
10
11
|
data?: any;
|
|
11
12
|
};
|
|
12
13
|
declare class KeyboardContext {
|
|
@@ -30,9 +31,10 @@ export declare class Keyboard {
|
|
|
30
31
|
destroy(): void;
|
|
31
32
|
bind: (config: KeyBinding) => void;
|
|
32
33
|
unbind: (binding: string) => void;
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
setCurrentContext(path?: string): void;
|
|
35
|
+
getCurrentContext(): string;
|
|
35
36
|
getContext(path?: string): KeyboardContext;
|
|
37
|
+
getBindings(): KeyBinding[];
|
|
36
38
|
handleKeyDown(event: KeyboardEvent): void;
|
|
37
39
|
}
|
|
38
40
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../src/keyboard.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,aAAa,CAAC;CACtB,KAAK,OAAO,GAAG,IAAI,CAAC;AAErB,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC;
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../src/keyboard.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,aAAa,CAAC;CACtB,KAAK,OAAO,GAAG,IAAI,CAAC;AAErB,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC;AAKF,cAAM,eAAe;IACnB,QAAQ,CAAC,OAAO,0BAAiC;IAEjD,IAAI,QAAQ,iBAEX;IAED,GAAG,CAAC,OAAO,EAAE,MAAM;IAInB,IAAI,CAAC,MAAM,EAAE,UAAU;IAavB,MAAM,CAAC,OAAO,EAAE,MAAM;CAGvB;AAID;;GAEG;AACH,qBAAa,QAAQ;IACnB,MAAM,CAAC,SAAS,WAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0D;IAClF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiC;IAC7D,OAAO,CAAC,KAAK,CAAQ;IAErB,UAAU;IAIV,OAAO;IAIP,IAAI,+BAAoC;IACxC,MAAM,4BAAsC;IAE5C,iBAAiB,CAAC,IAAI,SAAO;IAI7B,iBAAiB;IAIjB,UAAU,CAAC,IAAI,SAAO,GAAG,eAAe;IAYxC,WAAW;IAcX,aAAa,CAAC,KAAK,EAAE,aAAa;CAkCnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/keyboard",
|
|
3
|
-
"version": "0.3.10-main.
|
|
3
|
+
"version": "0.3.10-main.f460bb8",
|
|
4
4
|
"description": "Keyboard bindings",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@dxos/invariant": "0.3.10-main.
|
|
20
|
-
"@dxos/node-std": "0.3.10-main.
|
|
19
|
+
"@dxos/invariant": "0.3.10-main.f460bb8",
|
|
20
|
+
"@dxos/node-std": "0.3.10-main.f460bb8"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"react": "^18.2.0",
|
|
24
24
|
"react-dom": "^18.2.0",
|
|
25
|
-
"@dxos/react-ui": "0.3.10-main.
|
|
26
|
-
"@dxos/react-ui-theme": "0.3.10-main.
|
|
27
|
-
"@dxos/storybook-utils": "0.3.10-main.
|
|
25
|
+
"@dxos/react-ui": "0.3.10-main.f460bb8",
|
|
26
|
+
"@dxos/react-ui-theme": "0.3.10-main.f460bb8",
|
|
27
|
+
"@dxos/storybook-utils": "0.3.10-main.f460bb8"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
package/src/keyboard.stories.tsx
CHANGED
package/src/keyboard.ts
CHANGED
|
@@ -14,9 +14,13 @@ export type KeyHandler = (props: {
|
|
|
14
14
|
export type KeyBinding = {
|
|
15
15
|
binding: string;
|
|
16
16
|
handler: KeyHandler;
|
|
17
|
+
disableInput?: boolean;
|
|
17
18
|
data?: any;
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
// Keybinding is normalized to this order.
|
|
22
|
+
const modifiers = ['alt', 'ctrl', 'shift', 'meta'];
|
|
23
|
+
|
|
20
24
|
class KeyboardContext {
|
|
21
25
|
readonly _keyMap = new Map<string, KeyBinding>();
|
|
22
26
|
|
|
@@ -32,13 +36,12 @@ class KeyboardContext {
|
|
|
32
36
|
// Normalize order of modifiers.
|
|
33
37
|
const { binding } = config;
|
|
34
38
|
const parts = binding.split('+');
|
|
35
|
-
const mods =
|
|
39
|
+
const mods = modifiers.filter((key) => parts.includes(key));
|
|
36
40
|
invariant(mods.length === 0 || mods.length === parts.length - 1);
|
|
37
41
|
if (mods.length) {
|
|
38
42
|
config.binding = [...mods, parts[parts.length - 1]].join('+');
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
// console.log('bind', config);
|
|
42
45
|
this._keyMap.set(config.binding, config);
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -72,22 +75,12 @@ export class Keyboard {
|
|
|
72
75
|
bind = this._root.bind.bind(this._root);
|
|
73
76
|
unbind = this._root.unbind.bind(this._root);
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
setCurrentContext(path = ROOT) {
|
|
76
79
|
this._path = path;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
for (let i = 0; i < this._contexts.length; ++i) {
|
|
82
|
-
const path = this._contexts[i];
|
|
83
|
-
if (this._path.startsWith(path)) {
|
|
84
|
-
this.getContext(path).bindings.forEach((binding) => {
|
|
85
|
-
bindings.set(binding.binding, binding);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return Array.from(bindings.values());
|
|
82
|
+
getCurrentContext() {
|
|
83
|
+
return this._path;
|
|
91
84
|
}
|
|
92
85
|
|
|
93
86
|
getContext(path = ROOT): KeyboardContext {
|
|
@@ -102,18 +95,31 @@ export class Keyboard {
|
|
|
102
95
|
return context;
|
|
103
96
|
}
|
|
104
97
|
|
|
98
|
+
getBindings() {
|
|
99
|
+
const bindings = new Map<string, KeyBinding>();
|
|
100
|
+
for (let i = 0; i < this._contexts.length; ++i) {
|
|
101
|
+
const path = this._contexts[i];
|
|
102
|
+
if (this._path.startsWith(path)) {
|
|
103
|
+
this.getContext(path).bindings.forEach((binding) => {
|
|
104
|
+
bindings.set(binding.binding, binding);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Array.from(bindings.values());
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
handleKeyDown(event: KeyboardEvent) {
|
|
106
113
|
const { altKey, ctrlKey, metaKey, shiftKey, key } = event;
|
|
107
114
|
|
|
108
115
|
if (key !== 'Alt' && key !== 'Control' && key !== 'Meta' && key !== 'Shift') {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
const str = [altKey && 'alt', ctrlKey && 'ctrl', metaKey && 'meta', shiftKey && 'shift', key]
|
|
116
|
+
// Binding option to check for input or contenteditable.
|
|
117
|
+
const tagName = (event.target as any)?.tagName;
|
|
118
|
+
const isInput =
|
|
119
|
+
tagName === 'INPUT' || tagName === 'TEXTAREA' || (event.target as any)?.getAttribute('contenteditable');
|
|
120
|
+
|
|
121
|
+
// Normalized key binding.
|
|
122
|
+
const str = [altKey && 'alt', ctrlKey && 'ctrl', shiftKey && 'shift', metaKey && 'meta', key]
|
|
117
123
|
.filter(Boolean)
|
|
118
124
|
.join('+');
|
|
119
125
|
|
|
@@ -121,12 +127,12 @@ export class Keyboard {
|
|
|
121
127
|
for (let i = this._contexts.length - 1; i >= 0; --i) {
|
|
122
128
|
const path = this._contexts[i];
|
|
123
129
|
if (this._path.startsWith(path)) {
|
|
124
|
-
const { data, handler } = this.getContext(path).get(str) ?? {};
|
|
125
|
-
|
|
126
|
-
if (handler) {
|
|
130
|
+
const { data, handler, disableInput } = this.getContext(path).get(str) ?? {};
|
|
131
|
+
if (handler && (!isInput || !disableInput)) {
|
|
127
132
|
const result = handler({ context: path, binding: str, data, event });
|
|
128
133
|
if (result !== false) {
|
|
129
|
-
// TODO(burdon):
|
|
134
|
+
// TODO(burdon): This doesn't prevent actions in markdown editor.
|
|
135
|
+
// Reference: https://codemirror.net/docs/ref/#view.KeyBinding
|
|
130
136
|
event.preventDefault();
|
|
131
137
|
event.stopPropagation();
|
|
132
138
|
}
|