@dxos/keyboard 0.3.11-main.346d280 → 0.3.11-main.3a8a0ef

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/README.md CHANGED
@@ -1,28 +1,163 @@
1
- ## React Async
1
+ ## @dxos/keyboard
2
2
 
3
- ### Configuration
3
+ Utility for managing contextual keyboard bindings.
4
4
 
5
- Add the following to `package.json`.
5
+ ## Common Shortcuts
6
6
 
7
- ```json
8
- "toolchain": {
9
- "forceCloseTests": true,
10
- "jsdom": true,
11
- "testingFramework": "mocha"
12
- }
13
- ```
7
+ - Meta => Command (macos), Windows (Windows), Super (Linux)
8
+ - Alt => Option (macos)
9
+ - Tip: Use https://tableconvert.com/markdown-to-markdown to edit the table.
14
10
 
15
- The `react-dom/test-utils` requires the `raf` (RequestAnimationFrame) polyfill to run headless.
16
-
17
- ```ts
18
- import 'raf/polyfill';
19
- import { act } from 'react-dom/test-utils';
20
- ```
21
-
22
- ### Next
23
-
24
- Evaluate the following:
25
-
26
- - https://www.npmjs.com/package/react-async-hook (popular)
27
- - https://www.npmjs.com/package/use-enhanced-state
28
- - https://www.npmjs.com/package/react-hooks-lib
11
+ | | macos | windows | chrome (macos) | chrome (windows) |
12
+ |-------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------|----------------|-----------------------------------------------------------------------------------|
13
+ | | https://support.apple.com/en-us/HT201236 | https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec | | https://support.google.com/chrome/answer/157179?hl=en&co=GENIE.Platform%3DDesktop |
14
+ | Alt-Tab | | x | | |
15
+ | Alt-F4 | | x | | |
16
+ | Alt-F8 | | x | | |
17
+ | Alt-Esc | | x | | |
18
+ | Alt-Enter | | x | | |
19
+ | Alt-Space | | x | | |
20
+ | Alt-A | | x | | |
21
+ | Alt-E | | | | x |
22
+ | Alt-F | | | | x |
23
+ | Alt-Meta-Esc | x | | | |
24
+ | Alt-Meta-Enter | | x | | |
25
+ | Alt-Meta-B | | x | x | |
26
+ | Alt-Meta-F | | | x | |
27
+ | Alt-Meta-I | | | x | |
28
+ | Alt-Meta-J | | | x | |
29
+ | Alt-Meta-K | | x | | |
30
+ | Alt-Meta-P | | | x | |
31
+ | Alt-Meta-R | | x | | |
32
+ | Alt-Meta-U | | | x | |
33
+ | Alt-Meta-Shift-A | | | x | |
34
+ | Alt-Meta-Shift-I | | | x | |
35
+ | "Alt-Ctrl-Meta-," | x | | | |
36
+ | Alt-Ctrl-Meta-. | x | | | |
37
+ | Alt-Ctrl-Meta-8 | x | | | |
38
+ | Alt-Ctrl-Tab | | x | | |
39
+ | Alt-Shift-A | | | | x |
40
+ | Alt-Shift-I | | | | x |
41
+ | Alt-Shift-T | | | | x |
42
+ | Ctrl-Esc | | x | | |
43
+ | Ctrl-Backspace | | | | x |
44
+ | Ctrl-Space | | x | | |
45
+ | Ctrl-Tab | | | | x |
46
+ | Ctrl-+ | | | | x |
47
+ | Ctrl-- | | | | x |
48
+ | Ctrl-F2 | x | | x | |
49
+ | Ctrl-F3 | x | | | |
50
+ | Ctrl-F4 | x | | | |
51
+ | Ctrl-F5 | x | | | x |
52
+ | Ctrl-F6 | x | | | x |
53
+ | Ctrl-F7 | x | | | |
54
+ | Ctrl-F8 | x | | | |
55
+ | Ctrl-F4 | | x | | |
56
+ | Ctrl-A | x | x | | |
57
+ | Ctrl-B | x | | | |
58
+ | Ctrl-C | | x | | |
59
+ | Ctrl-D | | x | | x |
60
+ | Ctrl-E | x | | | x |
61
+ | Ctrl-F | x | | | x |
62
+ | Ctrl-G | | | | x |
63
+ | Ctrl-H | | | | x |
64
+ | Ctrl-J | | | | x |
65
+ | Ctrl-K | | | | x |
66
+ | Ctrl-L | x | | | x |
67
+ | Ctrl-N | x | | | x |
68
+ | Ctrl-O | x | | | x |
69
+ | Ctrl-P | x | | | x |
70
+ | Ctrl-R | | x | | x |
71
+ | Ctrl-S | | | | x |
72
+ | Ctrl-T | x | | | x |
73
+ | Ctrl-U | | | | x |
74
+ | Ctrl-V | | x | | |
75
+ | Ctrl-W | | | | x |
76
+ | Ctrl-X | | x | | |
77
+ | Ctrl-Y | | x | | |
78
+ | Ctrl-Z | | x | | |
79
+ | Ctrl-Meta-Enter | | | | |
80
+ | Ctrl-Meta-Space | x | | | |
81
+ | Ctrl-Meta-C | | x | | |
82
+ | Ctrl-Meta-Q | x | x | | |
83
+ | Ctrl-Meta-F | x | x | x | |
84
+ | Ctrl-Meta-Shift-B | | x | | |
85
+ | Ctrl-Shift-F6 | x | | | |
86
+ | Ctrl-Shift-Esc | | x | | |
87
+ | Ctrl-Shift-Delete | | | | x |
88
+ | Ctrl-Shift-B | | | | x |
89
+ | Ctrl-Shift-D | | | | x |
90
+ | Ctrl-Shift-G | | | | x |
91
+ | Ctrl-Shift-J | | | | x |
92
+ | Ctrl-Shift-M | | | | |
93
+ | Ctrl-Shift-N | | | | x |
94
+ | Ctrl-Shift-O | | | | x |
95
+ | Ctrl-Shift-R | | | | x |
96
+ | Ctrl-Shift-T | | | | x |
97
+ | Ctrl-Shift-V | | x | | |
98
+ | Meta-. | | x | | |
99
+ | Meta-; | | x | | |
100
+ | "Meta-," | | x | x | |
101
+ | Meta-` | x | | | |
102
+ | Meta-+ | | x | x | |
103
+ | Meta-- | | x | x | |
104
+ | Meta-/ | | x | | |
105
+ | Meta-[ | | | x | |
106
+ | Meta-] | | | x | |
107
+ | Meta-Backspace | x | | | |
108
+ | Meta-Esc | | x | | |
109
+ | Meta-Space | x | x | | |
110
+ | Meta-Tab | x | x | | |
111
+ | Meta-A | x | x | | |
112
+ | Meta-B | | x | | |
113
+ | Meta-C | x | x | | |
114
+ | Meta-D | | x | x | |
115
+ | Meta-E | | x | x | |
116
+ | Meta-F | x | x | x | |
117
+ | Meta-G | x | x | x | |
118
+ | Meta-H | x | x | x | |
119
+ | Meta-I | | x | | |
120
+ | Meta-J | | x | | |
121
+ | Meta-K | | x | | |
122
+ | Meta-L | | x | x | |
123
+ | Meta-M | x | x | x | |
124
+ | Meta-N | | x | x | |
125
+ | Meta-O | x | x | x | |
126
+ | Meta-P | x | x | x | |
127
+ | Meta-Q | x | x | x | |
128
+ | Meta-R | | x | | |
129
+ | Meta-S | x | x | x | |
130
+ | Meta-T | x | x | x | |
131
+ | Meta-U | | x | | |
132
+ | Meta-V | x | x | | |
133
+ | Meta-W | x | x | x | |
134
+ | Meta-X | x | x | | |
135
+ | Meta-Y | | x | x | |
136
+ | Meta-Z | x | x | | |
137
+ | Meta-Shift-Space | | x | | |
138
+ | Meta-Shift-Delete | | | x | |
139
+ | Meta-Shift-+ | x | | | |
140
+ | Meta-Shift-- | x | | | |
141
+ | Meta-Shift-? | x | | | |
142
+ | Meta-Shift-3 | x | | | |
143
+ | Meta-Shift-4 | x | | | |
144
+ | Meta-Shift-5 | x | | | |
145
+ | Meta-Shift-B | | | x | |
146
+ | Meta-Shift-C | | x | | |
147
+ | Meta-Shift-D | | | x | |
148
+ | Meta-Shift-G | | | x | |
149
+ | Meta-Shift-H | | | x | |
150
+ | Meta-Shift-I | | | x | |
151
+ | Meta-Shift-J | | | x | |
152
+ | Meta-Shift-M | | x | x | |
153
+ | Meta-Shift-N | | | x | |
154
+ | Meta-Shift-R | | | | |
155
+ | Meta-Shift-S | x | x | | |
156
+ | Meta-Shift-T | | | x | |
157
+ | Meta-Shift-V | | x | | |
158
+ | Meta-Shift-W | | | x | |
159
+ | Meta-Shift-Q | x | | | |
160
+ | Meta-Shift-` | x | | | |
161
+ | Alt-Meta-` | x | | | |
162
+ | Ctrl-Tab | x | | | |
163
+ | Ctrl-Shift-Tab | x | | | |
@@ -17,7 +17,7 @@ var modifiers = [
17
17
  "meta"
18
18
  ];
19
19
  var parseShortcut = (shortcut, delimiter = /[+-]/) => {
20
- const parts = shortcut.split(delimiter);
20
+ const parts = shortcut.toLowerCase().split(delimiter);
21
21
  const mods = modifiers.filter((key) => parts.includes(key));
22
22
  invariant(mods.length === 0 || mods.length === parts.length - 1, void 0, {
23
23
  F: __dxlog_file,
@@ -142,22 +142,58 @@ var Keyboard = class _Keyboard {
142
142
  };
143
143
 
144
144
  // packages/common/keyboard/src/util.ts
145
- var symbols = {
146
- // Mods.
147
- ctrl: "\u2303",
148
- shift: "\u21E7",
149
- alt: "\u2325",
150
- meta: "\u2318",
151
- // Special keys.
152
- Backspace: "\u232B",
153
- Enter: "\u23CE",
154
- Escape: "\u238B",
155
- Space: "\u2423",
156
- Tab: "\u21E5"
145
+ import { getHostPlatform } from "@dxos/util";
146
+ var alt = {
147
+ macos: "\u2325",
148
+ ios: "\u2325",
149
+ windows: "Alt",
150
+ linux: "Alt",
151
+ unknown: "Alt"
152
+ };
153
+ var ctrl = {
154
+ macos: "\u2303",
155
+ ios: "\u2303",
156
+ windows: "Ctrl",
157
+ linux: "Ctrl",
158
+ unknown: "Ctrl"
159
+ };
160
+ var meta = {
161
+ macos: "\u2318",
162
+ ios: "\u2318",
163
+ windows: "\u229E",
164
+ // TODO(wittjosiah): Use ⌘ or ⊞ instead? Wait for user feedback.
165
+ // From https://en.wikipedia.org/wiki/Super_key_(keyboard_button).
166
+ linux: "\u2756",
167
+ unknown: "\u2756"
168
+ };
169
+ var getSymbol = (part) => {
170
+ const platform = getHostPlatform();
171
+ switch (part) {
172
+ case "alt":
173
+ return alt[platform];
174
+ case "ctrl":
175
+ return ctrl[platform];
176
+ case "meta":
177
+ return meta[platform];
178
+ case "shift":
179
+ return "\u21E7";
180
+ case "Backspace":
181
+ return "\u232B";
182
+ case "Enter":
183
+ return "\u23CE";
184
+ case "Escape":
185
+ return "\u238B";
186
+ case "Space":
187
+ return "\u2423";
188
+ case "Tab":
189
+ return "\u21E5";
190
+ default:
191
+ return part.toUpperCase();
192
+ }
157
193
  };
158
194
  var keySymbols = (keyBinding) => {
159
195
  const parts = keyBinding.split("+");
160
- return parts.map((part) => symbols[part] ?? part.toUpperCase());
196
+ return parts.map(getSymbol);
161
197
  };
162
198
  export {
163
199
  Keyboard,
@@ -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 shortcut: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n shortcut: 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\n// Normalize order of modifiers.\nexport const parseShortcut = (shortcut: string, delimiter = /[+-]/): string => {\n const parts = shortcut.split(delimiter);\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n // Assume single natural key.\n return mods.length ? [...mods, parts[parts.length - 1]].join('+') : shortcut;\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 config.shortcut = parseShortcut(config.shortcut);\n this._keyMap.set(config.shortcut, 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.shortcut, 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, shortcut: 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": ";;;;;;;;;;AAIA,SAASA,iBAAiB;;AAiB1B,IAAMC,YAAY;EAAC;EAAO;EAAQ;EAAS;;AAGpC,IAAMC,gBAAgB,CAACC,UAAkBC,YAAY,WAAM;AAChE,QAAMC,QAAQF,SAASG,MAAMF,SAAAA;AAC7B,QAAMG,OAAON,UAAUO,OAAO,CAACC,QAAQJ,MAAMK,SAASD,GAAAA,CAAAA;AACtDT,YAAUO,KAAKI,WAAW,KAAKJ,KAAKI,WAAWN,MAAMM,SAAS,GAAA,QAAA;;;;;;;;;AAE9D,SAAOJ,KAAKI,SAAS;OAAIJ;IAAMF,MAAMA,MAAMM,SAAS,CAAA;IAAIC,KAAK,GAAA,IAAOT;AACtE;AAEA,IAAMU,kBAAN,MAAMA;EAAN;AACWC,mBAAU,oBAAIC,IAAAA;;EAEvB,IAAIC,WAAW;AACb,WAAOC,MAAMC,KAAK,KAAKJ,QAAQK,OAAM,CAAA;EACvC;EAEAC,IAAIC,SAAiB;AACnB,WAAO,KAAKP,QAAQM,IAAIC,OAAAA;EAC1B;EAEAC,KAAKC,QAAoB;AACvBA,WAAOpB,WAAWD,cAAcqB,OAAOpB,QAAQ;AAC/C,SAAKW,QAAQU,IAAID,OAAOpB,UAAUoB,MAAAA;EACpC;EAEAE,OAAOJ,SAAiB;AACtB,SAAKP,QAAQY,OAAOL,OAAAA;EACtB;AACF;AAEA,IAAMM,OAAO;AAKN,IAAMC,WAAN,MAAMA,UAAAA;EAAN;AAGYC,iBAAQ,IAAIhB,gBAAAA;AACZC,mBAAU,oBAAIC,IAA6B;MAAC;QAACY;QAAM,KAAKE;;KAAO;AAC/DC,qBAAsB;MAACH;;AACvBI,uBAAc,KAAKC,cAAcV,KAAK,IAAI;AACnDW,iBAAQN;AAUhBL,gBAAO,KAAKO,MAAMP,KAAKA,KAAK,KAAKO,KAAK;AACtCJ,kBAAS,KAAKI,MAAMJ,OAAOH,KAAK,KAAKO,KAAK;;EAjB1C;SAAOK,YAAY,IAAIN,UAAAA;;EAQvBO,aAAa;AACXC,aAASC,iBAAiB,WAAW,KAAKN,WAAW;EACvD;EAEAO,UAAU;AACRF,aAASG,oBAAoB,WAAW,KAAKR,WAAW;EAC1D;EAKAS,kBAAkBC,OAAOd,MAAM;AAC7B,SAAKM,QAAQQ;EACf;EAEAC,oBAAoB;AAClB,WAAO,KAAKT;EACd;EAEAU,WAAWF,OAAOd,MAAuB;AACvC,QAAIiB,UAAU,KAAK9B,QAAQM,IAAIqB,IAAAA;AAC/B,QAAI,CAACG,SAAS;AACZA,gBAAU,IAAI/B,gBAAAA;AACd,WAAKC,QAAQU,IAAIiB,MAAMG,OAAAA;AACvB,WAAKd,UAAUe,KAAKJ,IAAAA;AACpB,WAAKX,UAAUgB,KAAI;IACrB;AAEA,WAAOF;EACT;EAEAG,cAAc;AACZ,UAAM/B,WAAW,oBAAID,IAAAA;AACrB,aAASiC,IAAI,GAAGA,IAAI,KAAKlB,UAAUnB,QAAQ,EAAEqC,GAAG;AAC9C,YAAMP,OAAO,KAAKX,UAAUkB,CAAAA;AAC5B,UAAI,KAAKf,MAAMgB,WAAWR,IAAAA,GAAO;AAC/B,aAAKE,WAAWF,IAAAA,EAAMzB,SAASkC,QAAQ,CAAC7B,YAAAA;AACtCL,mBAASQ,IAAIH,QAAQlB,UAAUkB,OAAAA;QACjC,CAAA;MACF;IACF;AAEA,WAAOJ,MAAMC,KAAKF,SAASG,OAAM,CAAA;EACnC;EAEAa,cAAcmB,OAAsB;AAClC,UAAM,EAAEC,QAAQC,SAASC,SAASC,UAAU9C,IAAG,IAAK0C;AAEpD,QAAI1C,QAAQ,SAASA,QAAQ,aAAaA,QAAQ,UAAUA,QAAQ,SAAS;AAE3E,YAAM+C,UAAWL,MAAMM,QAAgBD;AACvC,YAAME,UACJF,YAAY,WAAWA,YAAY,cAAeL,MAAMM,QAAgBE,aAAa,iBAAA;AAGvF,YAAMC,MAAM;QAACR,UAAU;QAAOC,WAAW;QAAQE,YAAY;QAASD,WAAW;QAAQ7C;QACtFD,OAAOqD,OAAAA,EACPjD,KAAK,GAAA;AAGR,eAASoC,IAAI,KAAKlB,UAAUnB,SAAS,GAAGqC,KAAK,GAAG,EAAEA,GAAG;AACnD,cAAMP,OAAO,KAAKX,UAAUkB,CAAAA;AAC5B,YAAI,KAAKf,MAAMgB,WAAWR,IAAAA,GAAO;AAC/B,gBAAM,EAAEqB,MAAMC,SAASC,aAAY,IAAK,KAAKrB,WAAWF,IAAAA,EAAMrB,IAAIwC,GAAAA,KAAQ,CAAC;AAC3E,cAAIG,YAAY,CAACL,WAAW,CAACM,eAAe;AAC1C,kBAAMC,SAASF,QAAQ;cAAEnB,SAASH;cAAMtC,UAAUyD;cAAKE;cAAMX;YAAM,CAAA;AACnE,gBAAIc,WAAW,OAAO;AAGpBd,oBAAMe,eAAc;AACpBf,oBAAMgB,gBAAe;YACvB;AAEA;UACF;QACF;MACF;IACF;EACF;AACF;;;ACxIA,IAAMC,UAAkC;;EAEtCC,MAAM;EACNC,OAAO;EACPC,KAAK;EACLC,MAAM;;EAGNC,WAAW;EACXC,OAAO;EACPC,QAAQ;EACRC,OAAO;EACPC,KAAK;AACP;AAEO,IAAMC,aAAa,CAACC,eAAAA;AACzB,QAAMC,QAAQD,WAAWE,MAAM,GAAA;AAC/B,SAAOD,MAAME,IAAI,CAACC,SAASf,QAAQe,IAAAA,KAASA,KAAKC,YAAW,CAAA;AAC9D;",
6
- "names": ["invariant", "modifiers", "parseShortcut", "shortcut", "delimiter", "parts", "split", "mods", "filter", "key", "includes", "length", "join", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "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"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n shortcut: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n shortcut: 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\n// Normalize order of modifiers.\nexport const parseShortcut = (shortcut: string, delimiter = /[+-]/): string => {\n const parts = shortcut.toLowerCase().split(delimiter);\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n // Assume single natural key.\n return mods.length ? [...mods, parts[parts.length - 1]].join('+') : shortcut;\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 config.shortcut = parseShortcut(config.shortcut);\n this._keyMap.set(config.shortcut, 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.shortcut, 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, shortcut: 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\nimport { getHostPlatform } from '@dxos/util';\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\nconst alt: Record<string, string> = {\n macos: '⌥',\n ios: '⌥',\n windows: 'Alt',\n linux: 'Alt',\n unknown: 'Alt',\n};\n\nconst ctrl: Record<string, string> = {\n macos: '⌃',\n ios: '⌃',\n windows: 'Ctrl',\n linux: 'Ctrl',\n unknown: 'Ctrl',\n};\n\nconst meta: Record<string, string> = {\n macos: '⌘',\n ios: '⌘',\n windows: '⊞',\n // TODO(wittjosiah): Use ⌘ or ⊞ instead? Wait for user feedback.\n // From https://en.wikipedia.org/wiki/Super_key_(keyboard_button).\n linux: '❖',\n unknown: '❖',\n};\n\nconst getSymbol = (part: string) => {\n const platform = getHostPlatform();\n switch (part) {\n // Mods.\n case 'alt':\n return alt[platform];\n case 'ctrl':\n return ctrl[platform];\n case 'meta':\n return meta[platform];\n case 'shift':\n return '⇧';\n // Special keys.\n case 'Backspace':\n return '⌫';\n case 'Enter':\n return '⏎';\n case 'Escape':\n return '⎋';\n case 'Space':\n return '␣';\n case 'Tab':\n return '⇥';\n default:\n return part.toUpperCase();\n }\n};\n\nexport const keySymbols = (keyBinding: string): string[] => {\n const parts = keyBinding.split('+');\n return parts.map(getSymbol);\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["invariant", "modifiers", "parseShortcut", "shortcut", "delimiter", "parts", "toLowerCase", "split", "mods", "filter", "key", "includes", "length", "join", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "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", "getHostPlatform", "alt", "macos", "ios", "windows", "linux", "unknown", "ctrl", "meta", "getSymbol", "part", "platform", "getHostPlatform", "toUpperCase", "keySymbols", "keyBinding", "parts", "split", "map"]
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":15411,"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":8927},"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","parseShortcut"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"inject-globals:@inject-globals":{"bytesInOutput":79},"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3623},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":4307}}}
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":15477,"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":5339,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"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":10276},"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},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Keyboard","keySymbols","parseShortcut"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"inject-globals:@inject-globals":{"bytesInOutput":79},"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3637},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":1136}},"bytes":5095}}}
@@ -24,6 +24,7 @@ __export(node_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(node_exports);
26
26
  var import_invariant = require("@dxos/invariant");
27
+ var import_util = require("@dxos/util");
27
28
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/keyboard/src/keyboard.ts";
28
29
  var modifiers = [
29
30
  "alt",
@@ -32,7 +33,7 @@ var modifiers = [
32
33
  "meta"
33
34
  ];
34
35
  var parseShortcut = (shortcut, delimiter = /[+-]/) => {
35
- const parts = shortcut.split(delimiter);
36
+ const parts = shortcut.toLowerCase().split(delimiter);
36
37
  const mods = modifiers.filter((key) => parts.includes(key));
37
38
  (0, import_invariant.invariant)(mods.length === 0 || mods.length === parts.length - 1, void 0, {
38
39
  F: __dxlog_file,
@@ -155,22 +156,57 @@ var Keyboard = class _Keyboard {
155
156
  }
156
157
  }
157
158
  };
158
- var symbols = {
159
- // Mods.
160
- ctrl: "\u2303",
161
- shift: "\u21E7",
162
- alt: "\u2325",
163
- meta: "\u2318",
164
- // Special keys.
165
- Backspace: "\u232B",
166
- Enter: "\u23CE",
167
- Escape: "\u238B",
168
- Space: "\u2423",
169
- Tab: "\u21E5"
159
+ var alt = {
160
+ macos: "\u2325",
161
+ ios: "\u2325",
162
+ windows: "Alt",
163
+ linux: "Alt",
164
+ unknown: "Alt"
165
+ };
166
+ var ctrl = {
167
+ macos: "\u2303",
168
+ ios: "\u2303",
169
+ windows: "Ctrl",
170
+ linux: "Ctrl",
171
+ unknown: "Ctrl"
172
+ };
173
+ var meta = {
174
+ macos: "\u2318",
175
+ ios: "\u2318",
176
+ windows: "\u229E",
177
+ // TODO(wittjosiah): Use ⌘ or ⊞ instead? Wait for user feedback.
178
+ // From https://en.wikipedia.org/wiki/Super_key_(keyboard_button).
179
+ linux: "\u2756",
180
+ unknown: "\u2756"
181
+ };
182
+ var getSymbol = (part) => {
183
+ const platform = (0, import_util.getHostPlatform)();
184
+ switch (part) {
185
+ case "alt":
186
+ return alt[platform];
187
+ case "ctrl":
188
+ return ctrl[platform];
189
+ case "meta":
190
+ return meta[platform];
191
+ case "shift":
192
+ return "\u21E7";
193
+ case "Backspace":
194
+ return "\u232B";
195
+ case "Enter":
196
+ return "\u23CE";
197
+ case "Escape":
198
+ return "\u238B";
199
+ case "Space":
200
+ return "\u2423";
201
+ case "Tab":
202
+ return "\u21E5";
203
+ default:
204
+ return part.toUpperCase();
205
+ }
170
206
  };
171
207
  var keySymbols = (keyBinding) => {
172
208
  const parts = keyBinding.split("+");
173
- return parts.map((part) => symbols[part] ?? part.toUpperCase());
209
+ return parts.map(getSymbol);
174
210
  };
175
211
  // Annotate the CommonJS export names for ESM import in node:
176
212
  0 && (module.exports = {
@@ -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 shortcut: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n shortcut: 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\n// Normalize order of modifiers.\nexport const parseShortcut = (shortcut: string, delimiter = /[+-]/): string => {\n const parts = shortcut.split(delimiter);\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n // Assume single natural key.\n return mods.length ? [...mods, parts[parts.length - 1]].join('+') : shortcut;\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 config.shortcut = parseShortcut(config.shortcut);\n this._keyMap.set(config.shortcut, 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.shortcut, 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, shortcut: 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": ";;;;;;;;;;;;;;;;;;;;;;;;;AAIA,uBAA0B;;AAiB1B,IAAMA,YAAY;EAAC;EAAO;EAAQ;EAAS;;AAGpC,IAAMC,gBAAgB,CAACC,UAAkBC,YAAY,WAAM;AAChE,QAAMC,QAAQF,SAASG,MAAMF,SAAAA;AAC7B,QAAMG,OAAON,UAAUO,OAAO,CAACC,QAAQJ,MAAMK,SAASD,GAAAA,CAAAA;AACtDE,kCAAUJ,KAAKK,WAAW,KAAKL,KAAKK,WAAWP,MAAMO,SAAS,GAAA,QAAA;;;;;;;;;AAE9D,SAAOL,KAAKK,SAAS;OAAIL;IAAMF,MAAMA,MAAMO,SAAS,CAAA;IAAIC,KAAK,GAAA,IAAOV;AACtE;AAEA,IAAMW,kBAAN,MAAMA;EAAN,cAAA;AACWC,SAAAA,UAAU,oBAAIC,IAAAA;;EAEvB,IAAIC,WAAW;AACb,WAAOC,MAAMC,KAAK,KAAKJ,QAAQK,OAAM,CAAA;EACvC;EAEAC,IAAIC,SAAiB;AACnB,WAAO,KAAKP,QAAQM,IAAIC,OAAAA;EAC1B;EAEAC,KAAKC,QAAoB;AACvBA,WAAOrB,WAAWD,cAAcsB,OAAOrB,QAAQ;AAC/C,SAAKY,QAAQU,IAAID,OAAOrB,UAAUqB,MAAAA;EACpC;EAEAE,OAAOJ,SAAiB;AACtB,SAAKP,QAAQY,OAAOL,OAAAA;EACtB;AACF;AAEA,IAAMM,OAAO;AAKN,IAAMC,WAAN,MAAMA,UAAAA;EAAN,cAAA;AAGYC,SAAAA,QAAQ,IAAIhB,gBAAAA;AACZC,SAAAA,UAAU,oBAAIC,IAA6B;MAAC;QAACY;QAAM,KAAKE;;KAAO;AAC/DC,SAAAA,YAAsB;MAACH;;AACvBI,SAAAA,cAAc,KAAKC,cAAcV,KAAK,IAAI;AACnDW,SAAAA,QAAQN;AAUhBL,SAAAA,OAAO,KAAKO,MAAMP,KAAKA,KAAK,KAAKO,KAAK;AACtCJ,SAAAA,SAAS,KAAKI,MAAMJ,OAAOH,KAAK,KAAKO,KAAK;;EAjB1C,OAAA;SAAOK,YAAY,IAAIN,UAAAA;;EAQvBO,aAAa;AACXC,aAASC,iBAAiB,WAAW,KAAKN,WAAW;EACvD;EAEAO,UAAU;AACRF,aAASG,oBAAoB,WAAW,KAAKR,WAAW;EAC1D;EAKAS,kBAAkBC,OAAOd,MAAM;AAC7B,SAAKM,QAAQQ;EACf;EAEAC,oBAAoB;AAClB,WAAO,KAAKT;EACd;EAEAU,WAAWF,OAAOd,MAAuB;AACvC,QAAIiB,UAAU,KAAK9B,QAAQM,IAAIqB,IAAAA;AAC/B,QAAI,CAACG,SAAS;AACZA,gBAAU,IAAI/B,gBAAAA;AACd,WAAKC,QAAQU,IAAIiB,MAAMG,OAAAA;AACvB,WAAKd,UAAUe,KAAKJ,IAAAA;AACpB,WAAKX,UAAUgB,KAAI;IACrB;AAEA,WAAOF;EACT;EAEAG,cAAc;AACZ,UAAM/B,WAAW,oBAAID,IAAAA;AACrB,aAASiC,IAAI,GAAGA,IAAI,KAAKlB,UAAUnB,QAAQ,EAAEqC,GAAG;AAC9C,YAAMP,OAAO,KAAKX,UAAUkB,CAAAA;AAC5B,UAAI,KAAKf,MAAMgB,WAAWR,IAAAA,GAAO;AAC/B,aAAKE,WAAWF,IAAAA,EAAMzB,SAASkC,QAAQ,CAAC7B,YAAAA;AACtCL,mBAASQ,IAAIH,QAAQnB,UAAUmB,OAAAA;QACjC,CAAA;MACF;IACF;AAEA,WAAOJ,MAAMC,KAAKF,SAASG,OAAM,CAAA;EACnC;EAEAa,cAAcmB,OAAsB;AAClC,UAAM,EAAEC,QAAQC,SAASC,SAASC,UAAU/C,IAAG,IAAK2C;AAEpD,QAAI3C,QAAQ,SAASA,QAAQ,aAAaA,QAAQ,UAAUA,QAAQ,SAAS;AAE3E,YAAMgD,UAAWL,MAAMM,QAAgBD;AACvC,YAAME,UACJF,YAAY,WAAWA,YAAY,cAAeL,MAAMM,QAAgBE,aAAa,iBAAA;AAGvF,YAAMC,MAAM;QAACR,UAAU;QAAOC,WAAW;QAAQE,YAAY;QAASD,WAAW;QAAQ9C;QACtFD,OAAOsD,OAAAA,EACPjD,KAAK,GAAA;AAGR,eAASoC,IAAI,KAAKlB,UAAUnB,SAAS,GAAGqC,KAAK,GAAG,EAAEA,GAAG;AACnD,cAAMP,OAAO,KAAKX,UAAUkB,CAAAA;AAC5B,YAAI,KAAKf,MAAMgB,WAAWR,IAAAA,GAAO;AAC/B,gBAAM,EAAEqB,MAAMC,SAASC,aAAY,IAAK,KAAKrB,WAAWF,IAAAA,EAAMrB,IAAIwC,GAAAA,KAAQ,CAAC;AAC3E,cAAIG,YAAY,CAACL,WAAW,CAACM,eAAe;AAC1C,kBAAMC,SAASF,QAAQ;cAAEnB,SAASH;cAAMvC,UAAU0D;cAAKE;cAAMX;YAAM,CAAA;AACnE,gBAAIc,WAAW,OAAO;AAGpBd,oBAAMe,eAAc;AACpBf,oBAAMgB,gBAAe;YACvB;AAEA;UACF;QACF;MACF;IACF;EACF;AACF;ACxIA,IAAMC,UAAkC;;EAEtCC,MAAM;EACNC,OAAO;EACPC,KAAK;EACLC,MAAM;;EAGNC,WAAW;EACXC,OAAO;EACPC,QAAQ;EACRC,OAAO;EACPC,KAAK;AACP;AAEO,IAAMC,aAAa,CAACC,eAAAA;AACzB,QAAM3E,QAAQ2E,WAAW1E,MAAM,GAAA;AAC/B,SAAOD,MAAM4E,IAAI,CAACC,SAASb,QAAQa,IAAAA,KAASA,KAAKC,YAAW,CAAA;AAC9D;",
6
- "names": ["modifiers", "parseShortcut", "shortcut", "delimiter", "parts", "split", "mods", "filter", "key", "includes", "invariant", "length", "join", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "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"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\n\nexport type KeyHandler = (props: {\n context: string;\n shortcut: string;\n data?: any;\n event: KeyboardEvent;\n}) => boolean | void;\n\nexport type KeyBinding = {\n shortcut: 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\n// Normalize order of modifiers.\nexport const parseShortcut = (shortcut: string, delimiter = /[+-]/): string => {\n const parts = shortcut.toLowerCase().split(delimiter);\n const mods = modifiers.filter((key) => parts.includes(key));\n invariant(mods.length === 0 || mods.length === parts.length - 1);\n // Assume single natural key.\n return mods.length ? [...mods, parts[parts.length - 1]].join('+') : shortcut;\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 config.shortcut = parseShortcut(config.shortcut);\n this._keyMap.set(config.shortcut, 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.shortcut, 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, shortcut: 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\nimport { getHostPlatform } from '@dxos/util';\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\nconst alt: Record<string, string> = {\n macos: '⌥',\n ios: '⌥',\n windows: 'Alt',\n linux: 'Alt',\n unknown: 'Alt',\n};\n\nconst ctrl: Record<string, string> = {\n macos: '⌃',\n ios: '⌃',\n windows: 'Ctrl',\n linux: 'Ctrl',\n unknown: 'Ctrl',\n};\n\nconst meta: Record<string, string> = {\n macos: '⌘',\n ios: '⌘',\n windows: '⊞',\n // TODO(wittjosiah): Use ⌘ or ⊞ instead? Wait for user feedback.\n // From https://en.wikipedia.org/wiki/Super_key_(keyboard_button).\n linux: '❖',\n unknown: '❖',\n};\n\nconst getSymbol = (part: string) => {\n const platform = getHostPlatform();\n switch (part) {\n // Mods.\n case 'alt':\n return alt[platform];\n case 'ctrl':\n return ctrl[platform];\n case 'meta':\n return meta[platform];\n case 'shift':\n return '⇧';\n // Special keys.\n case 'Backspace':\n return '⌫';\n case 'Enter':\n return '⏎';\n case 'Escape':\n return '⎋';\n case 'Space':\n return '␣';\n case 'Tab':\n return '⇥';\n default:\n return part.toUpperCase();\n }\n};\n\nexport const keySymbols = (keyBinding: string): string[] => {\n const parts = keyBinding.split('+');\n return parts.map(getSymbol);\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["modifiers", "parseShortcut", "shortcut", "delimiter", "parts", "toLowerCase", "split", "mods", "filter", "key", "includes", "invariant", "length", "join", "KeyboardContext", "_keyMap", "Map", "bindings", "Array", "from", "values", "get", "binding", "bind", "config", "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", "alt", "macos", "ios", "windows", "linux", "unknown", "ctrl", "meta", "getSymbol", "part", "platform", "getHostPlatform", "toUpperCase", "keySymbols", "keyBinding", "map"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytes":15411,"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":8918},"packages/common/keyboard/dist/lib/node/index.cjs":{"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true}],"exports":["Keyboard","keySymbols","parseShortcut"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3623},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":362}},"bytes":4159}}}
1
+ {"inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytes":15477,"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true}],"format":"esm"},"packages/common/keyboard/src/util.ts":{"bytes":5339,"imports":[{"path":"@dxos/util","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"}],"format":"esm"}},"outputs":{"packages/common/keyboard/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":10267},"packages/common/keyboard/dist/lib/node/index.cjs":{"imports":[{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Keyboard","keySymbols","parseShortcut"],"entryPoint":"packages/common/keyboard/src/index.ts","inputs":{"packages/common/keyboard/src/keyboard.ts":{"bytesInOutput":3637},"packages/common/keyboard/src/index.ts":{"bytesInOutput":0},"packages/common/keyboard/src/util.ts":{"bytesInOutput":1136}},"bytes":4947}}}
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,UAAU,eAAgB,MAAM,KAAG,MAAM,EAGrD,CAAC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAiEA,eAAO,MAAM,UAAU,eAAgB,MAAM,KAAG,MAAM,EAGrD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/keyboard",
3
- "version": "0.3.11-main.346d280",
3
+ "version": "0.3.11-main.3a8a0ef",
4
4
  "description": "Keyboard bindings",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -16,15 +16,16 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@dxos/invariant": "0.3.11-main.346d280",
20
- "@dxos/node-std": "0.3.11-main.346d280"
19
+ "@dxos/node-std": "0.3.11-main.3a8a0ef",
20
+ "@dxos/util": "0.3.11-main.3a8a0ef",
21
+ "@dxos/invariant": "0.3.11-main.3a8a0ef"
21
22
  },
22
23
  "devDependencies": {
23
24
  "react": "^18.2.0",
24
25
  "react-dom": "^18.2.0",
25
- "@dxos/react-ui-theme": "0.3.11-main.346d280",
26
- "@dxos/react-ui": "0.3.11-main.346d280",
27
- "@dxos/storybook-utils": "0.3.11-main.346d280"
26
+ "@dxos/react-ui-theme": "0.3.11-main.3a8a0ef",
27
+ "@dxos/react-ui": "0.3.11-main.3a8a0ef",
28
+ "@dxos/storybook-utils": "0.3.11-main.3a8a0ef"
28
29
  },
29
30
  "publishConfig": {
30
31
  "access": "public"
package/src/keyboard.ts CHANGED
@@ -23,7 +23,7 @@ const modifiers = ['alt', 'ctrl', 'shift', 'meta'];
23
23
 
24
24
  // Normalize order of modifiers.
25
25
  export const parseShortcut = (shortcut: string, delimiter = /[+-]/): string => {
26
- const parts = shortcut.split(delimiter);
26
+ const parts = shortcut.toLowerCase().split(delimiter);
27
27
  const mods = modifiers.filter((key) => parts.includes(key));
28
28
  invariant(mods.length === 0 || mods.length === parts.length - 1);
29
29
  // Assume single natural key.
package/src/util.ts CHANGED
@@ -2,28 +2,68 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { getHostPlatform } from '@dxos/util';
6
+
5
7
  // https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents
6
8
  // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
7
9
  // https://support.apple.com/en-us/HT201236
8
10
  // https://support.apple.com/guide/mac-help/what-are-those-symbols-shown-in-menus-cpmh0011/mac
9
11
 
10
- // TODO(burdon): Show Mac/Windows/Linux variants.
11
- const symbols: Record<string, string> = {
12
- // Mods.
13
- ctrl: '',
14
- shift: '',
15
- alt: '',
16
- meta: '⌘',
12
+ const alt: Record<string, string> = {
13
+ macos: '⌥',
14
+ ios: '⌥',
15
+ windows: 'Alt',
16
+ linux: 'Alt',
17
+ unknown: 'Alt',
18
+ };
19
+
20
+ const ctrl: Record<string, string> = {
21
+ macos: '⌃',
22
+ ios: '⌃',
23
+ windows: 'Ctrl',
24
+ linux: 'Ctrl',
25
+ unknown: 'Ctrl',
26
+ };
27
+
28
+ const meta: Record<string, string> = {
29
+ macos: '⌘',
30
+ ios: '⌘',
31
+ windows: '⊞',
32
+ // TODO(wittjosiah): Use ⌘ or ⊞ instead? Wait for user feedback.
33
+ // From https://en.wikipedia.org/wiki/Super_key_(keyboard_button).
34
+ linux: '❖',
35
+ unknown: '❖',
36
+ };
17
37
 
18
- // Special keys.
19
- Backspace: '⌫',
20
- Enter: '⏎',
21
- Escape: '⎋',
22
- Space: '',
23
- Tab: '⇥',
38
+ const getSymbol = (part: string) => {
39
+ const platform = getHostPlatform();
40
+ switch (part) {
41
+ // Mods.
42
+ case 'alt':
43
+ return alt[platform];
44
+ case 'ctrl':
45
+ return ctrl[platform];
46
+ case 'meta':
47
+ return meta[platform];
48
+ case 'shift':
49
+ return '⇧';
50
+ // Special keys.
51
+ case 'Backspace':
52
+ return '⌫';
53
+ case 'Enter':
54
+ return '⏎';
55
+ case 'Escape':
56
+ return '⎋';
57
+ case 'Space':
58
+ return '␣';
59
+ case 'Tab':
60
+ return '⇥';
61
+ default:
62
+ return part.toUpperCase();
63
+ }
24
64
  };
25
65
 
26
66
  export const keySymbols = (keyBinding: string): string[] => {
27
67
  const parts = keyBinding.split('+');
28
- return parts.map((part) => symbols[part] ?? part.toUpperCase());
68
+ return parts.map(getSymbol);
29
69
  };