@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.
@@ -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 = parts.filter((key) => [
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: 36,
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
- setContext(path = ROOT) {
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 = parts.filter((key) => ['alt', 'ctrl', 'meta', 'shift'].includes(key)).sort();\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 // console.log('bind', config);\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 setContext(path = ROOT) {\n this._path = path;\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 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 handleKeyDown(event: KeyboardEvent) {\n const { altKey, ctrlKey, metaKey, shiftKey, key } = event;\n\n if (key !== 'Alt' && key !== 'Control' && key !== 'Meta' && key !== 'Shift') {\n // TODO(burdon): Option to call anywhere.\n // TODO(burdon): Check for contenteditable.\n // const tagName = (event.target as any)?.tagName;\n // if (tagName === 'INPUT' || tagName === 'TEXTAREA') {\n // return;\n // }\n\n const str = [altKey && 'alt', ctrlKey && 'ctrl', metaKey && 'meta', shiftKey && 'shift', 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 } = this.getContext(path).get(str) ?? {};\n // console.log('>>>', path, str, handler);\n if (handler) {\n const result = handler({ context: path, binding: str, data, event });\n if (result !== false) {\n // TODO(burdon): Doesn't prevent actions in markdown editor.\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", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "sort", "length", "join", "set", "unbind", "delete", "ROOT", "Keyboard", "_root", "_contexts", "_keyHandler", "handleKeyDown", "_path", "singleton", "initialize", "document", "addEventListener", "destroy", "removeEventListener", "setContext", "path", "getBindings", "i", "startsWith", "getContext", "forEach", "context", "push", "event", "altKey", "ctrlKey", "metaKey", "shiftKey", "str", "Boolean", "data", "handler", "result", "preventDefault", "stopPropagation", "symbols", "ctrl", "shift", "alt", "meta", "Backspace", "Enter", "Escape", "Space", "Tab", "keySymbols", "keyBinding", "parts", "split", "map", "part", "toUpperCase"]
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":14347,"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":8209},"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":3316},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":3983}}}
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}}}
@@ -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 = parts.filter((key) => [
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: 36,
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
- setContext(path = ROOT) {
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 = parts.filter((key) => ['alt', 'ctrl', 'meta', 'shift'].includes(key)).sort();\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 // console.log('bind', config);\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 setContext(path = ROOT) {\n this._path = path;\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 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 handleKeyDown(event: KeyboardEvent) {\n const { altKey, ctrlKey, metaKey, shiftKey, key } = event;\n\n if (key !== 'Alt' && key !== 'Control' && key !== 'Meta' && key !== 'Shift') {\n // TODO(burdon): Option to call anywhere.\n // TODO(burdon): Check for contenteditable.\n // const tagName = (event.target as any)?.tagName;\n // if (tagName === 'INPUT' || tagName === 'TEXTAREA') {\n // return;\n // }\n\n const str = [altKey && 'alt', ctrlKey && 'ctrl', metaKey && 'meta', shiftKey && 'shift', 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 } = this.getContext(path).get(str) ?? {};\n // console.log('>>>', path, str, handler);\n if (handler) {\n const result = handler({ context: path, binding: str, data, event });\n if (result !== false) {\n // TODO(burdon): Doesn't prevent actions in markdown editor.\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": ["KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "parts", "split", "mods", "filter", "key", "includes", "sort", "invariant", "length", "join", "set", "unbind", "delete", "ROOT", "Keyboard", "_root", "_contexts", "_keyHandler", "handleKeyDown", "_path", "singleton", "initialize", "document", "addEventListener", "destroy", "removeEventListener", "setContext", "path", "getBindings", "i", "startsWith", "getContext", "forEach", "context", "push", "event", "altKey", "ctrlKey", "metaKey", "shiftKey", "str", "Boolean", "data", "handler", "result", "preventDefault", "stopPropagation", "symbols", "ctrl", "shift", "alt", "meta", "Backspace", "Enter", "Escape", "Space", "Tab", "keySymbols", "keyBinding", "map", "part", "toUpperCase"]
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
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytes":14347,"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":8200},"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":3316},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":3835}}}
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
- setContext(path?: string): void;
34
- getBindings(): KeyBinding[];
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;AAEF,cAAM,eAAe;IACnB,QAAQ,CAAC,OAAO,0BAAiC;IAEjD,IAAI,QAAQ,iBAEX;IAED,GAAG,CAAC,OAAO,EAAE,MAAM;IAInB,IAAI,CAAC,MAAM,EAAE,UAAU;IAcvB,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,UAAU,CAAC,IAAI,SAAO;IAItB,WAAW;IAcX,UAAU,CAAC,IAAI,SAAO,GAAG,eAAe;IAYxC,aAAa,CAAC,KAAK,EAAE,aAAa;CAmCnC"}
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.dcfebef",
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.dcfebef",
20
- "@dxos/node-std": "0.3.10-main.dcfebef"
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.dcfebef",
26
- "@dxos/react-ui-theme": "0.3.10-main.dcfebef",
27
- "@dxos/storybook-utils": "0.3.10-main.dcfebef"
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"
@@ -32,7 +32,7 @@ const Story = () => {
32
32
  }, []);
33
33
 
34
34
  const handleChange = (path: string) => {
35
- Keyboard.singleton.setContext(path);
35
+ Keyboard.singleton.setCurrentContext(path);
36
36
  setContext(path);
37
37
  setBindings(Keyboard.singleton.getBindings());
38
38
  };
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 = parts.filter((key) => ['alt', 'ctrl', 'meta', 'shift'].includes(key)).sort();
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
- setContext(path = ROOT) {
78
+ setCurrentContext(path = ROOT) {
76
79
  this._path = path;
77
80
  }
78
81
 
79
- getBindings() {
80
- const bindings = new Map<string, KeyBinding>();
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
- // TODO(burdon): Option to call anywhere.
110
- // TODO(burdon): Check for contenteditable.
111
- // const tagName = (event.target as any)?.tagName;
112
- // if (tagName === 'INPUT' || tagName === 'TEXTAREA') {
113
- // return;
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
- // console.log('>>>', path, str, handler);
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): Doesn't prevent actions in markdown editor.
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
  }