xref: /aosp_15_r20/external/perfetto/ui/src/base/hotkeys.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2023 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Worker// This module provides hotkey detection using type-safe human-readable strings.
16*6dbdd20aSAndroid Build Coastguard Worker//
17*6dbdd20aSAndroid Build Coastguard Worker// The basic premise is this: Let's say you have a KeyboardEvent |event|, and
18*6dbdd20aSAndroid Build Coastguard Worker// you wanted to check whether it contains the hotkey 'Ctrl+O', you can execute
19*6dbdd20aSAndroid Build Coastguard Worker// the following function:
20*6dbdd20aSAndroid Build Coastguard Worker//
21*6dbdd20aSAndroid Build Coastguard Worker//   checkHotkey('Shift+O', event);
22*6dbdd20aSAndroid Build Coastguard Worker//
23*6dbdd20aSAndroid Build Coastguard Worker// ...which will evaluate to true if 'Shift+O' is discovered in the event.
24*6dbdd20aSAndroid Build Coastguard Worker//
25*6dbdd20aSAndroid Build Coastguard Worker// This will only trigger when O is pressed while the Shift key is held, not O
26*6dbdd20aSAndroid Build Coastguard Worker// on it's own, and not if other modifiers such as Alt or Ctrl were also held.
27*6dbdd20aSAndroid Build Coastguard Worker//
28*6dbdd20aSAndroid Build Coastguard Worker// Modifiers include 'Shift', 'Ctrl', 'Alt', and 'Mod':
29*6dbdd20aSAndroid Build Coastguard Worker// - 'Shift' and 'Ctrl' are fairly self explanatory.
30*6dbdd20aSAndroid Build Coastguard Worker// - 'Alt' is 'option' on Macs.
31*6dbdd20aSAndroid Build Coastguard Worker// - 'Mod' is a special modifier which means 'Ctrl' on PC and 'Cmd' on Mac.
32*6dbdd20aSAndroid Build Coastguard Worker// Modifiers may be combined in various ways - check the |Modifier| type.
33*6dbdd20aSAndroid Build Coastguard Worker//
34*6dbdd20aSAndroid Build Coastguard Worker// By default hotkeys will not register when the event target is inside an
35*6dbdd20aSAndroid Build Coastguard Worker// editable element, such as <textarea> and some <input>s.
36*6dbdd20aSAndroid Build Coastguard Worker// Prefixing a hotkey with a bang '!' relaxes is requirement, meaning the hotkey
37*6dbdd20aSAndroid Build Coastguard Worker// will register inside editable fields.
38*6dbdd20aSAndroid Build Coastguard Worker
39*6dbdd20aSAndroid Build Coastguard Worker// E.g. '!Mod+Shift+P' will register when pressed when a text box has focus but
40*6dbdd20aSAndroid Build Coastguard Worker// 'Mod+Shift+P' (no bang) will not.
41*6dbdd20aSAndroid Build Coastguard Worker// Warning: Be careful using this with single key hotkeys, e.g. '!P' is usually
42*6dbdd20aSAndroid Build Coastguard Worker// never what you want!
43*6dbdd20aSAndroid Build Coastguard Worker//
44*6dbdd20aSAndroid Build Coastguard Worker// Some single-key hotkeys like '?' and '!' normally cannot be activated in
45*6dbdd20aSAndroid Build Coastguard Worker// without also pressing shift key, so the shift requirement is relaxed for
46*6dbdd20aSAndroid Build Coastguard Worker// these keys.
47*6dbdd20aSAndroid Build Coastguard Worker
48*6dbdd20aSAndroid Build Coastguard Workerimport {elementIsEditable} from './dom_utils';
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Workertype Alphabet =
51*6dbdd20aSAndroid Build Coastguard Worker  | 'A'
52*6dbdd20aSAndroid Build Coastguard Worker  | 'B'
53*6dbdd20aSAndroid Build Coastguard Worker  | 'C'
54*6dbdd20aSAndroid Build Coastguard Worker  | 'D'
55*6dbdd20aSAndroid Build Coastguard Worker  | 'E'
56*6dbdd20aSAndroid Build Coastguard Worker  | 'F'
57*6dbdd20aSAndroid Build Coastguard Worker  | 'G'
58*6dbdd20aSAndroid Build Coastguard Worker  | 'H'
59*6dbdd20aSAndroid Build Coastguard Worker  | 'I'
60*6dbdd20aSAndroid Build Coastguard Worker  | 'J'
61*6dbdd20aSAndroid Build Coastguard Worker  | 'K'
62*6dbdd20aSAndroid Build Coastguard Worker  | 'L'
63*6dbdd20aSAndroid Build Coastguard Worker  | 'M'
64*6dbdd20aSAndroid Build Coastguard Worker  | 'N'
65*6dbdd20aSAndroid Build Coastguard Worker  | 'O'
66*6dbdd20aSAndroid Build Coastguard Worker  | 'P'
67*6dbdd20aSAndroid Build Coastguard Worker  | 'Q'
68*6dbdd20aSAndroid Build Coastguard Worker  | 'R'
69*6dbdd20aSAndroid Build Coastguard Worker  | 'S'
70*6dbdd20aSAndroid Build Coastguard Worker  | 'T'
71*6dbdd20aSAndroid Build Coastguard Worker  | 'U'
72*6dbdd20aSAndroid Build Coastguard Worker  | 'V'
73*6dbdd20aSAndroid Build Coastguard Worker  | 'W'
74*6dbdd20aSAndroid Build Coastguard Worker  | 'X'
75*6dbdd20aSAndroid Build Coastguard Worker  | 'Y'
76*6dbdd20aSAndroid Build Coastguard Worker  | 'Z';
77*6dbdd20aSAndroid Build Coastguard Workertype Number = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
78*6dbdd20aSAndroid Build Coastguard Workertype Special =
79*6dbdd20aSAndroid Build Coastguard Worker  | 'Enter'
80*6dbdd20aSAndroid Build Coastguard Worker  | 'Escape'
81*6dbdd20aSAndroid Build Coastguard Worker  | 'Delete'
82*6dbdd20aSAndroid Build Coastguard Worker  | '/'
83*6dbdd20aSAndroid Build Coastguard Worker  | '?'
84*6dbdd20aSAndroid Build Coastguard Worker  | '!'
85*6dbdd20aSAndroid Build Coastguard Worker  | 'Space'
86*6dbdd20aSAndroid Build Coastguard Worker  | 'ArrowUp'
87*6dbdd20aSAndroid Build Coastguard Worker  | 'ArrowDown'
88*6dbdd20aSAndroid Build Coastguard Worker  | 'ArrowLeft'
89*6dbdd20aSAndroid Build Coastguard Worker  | 'ArrowRight'
90*6dbdd20aSAndroid Build Coastguard Worker  | '['
91*6dbdd20aSAndroid Build Coastguard Worker  | ']'
92*6dbdd20aSAndroid Build Coastguard Worker  | ','
93*6dbdd20aSAndroid Build Coastguard Worker  | '.';
94*6dbdd20aSAndroid Build Coastguard Workerexport type Key = Alphabet | Number | Special;
95*6dbdd20aSAndroid Build Coastguard Workerexport type Modifier =
96*6dbdd20aSAndroid Build Coastguard Worker  | ''
97*6dbdd20aSAndroid Build Coastguard Worker  | 'Mod+'
98*6dbdd20aSAndroid Build Coastguard Worker  | 'Shift+'
99*6dbdd20aSAndroid Build Coastguard Worker  | 'Ctrl+'
100*6dbdd20aSAndroid Build Coastguard Worker  | 'Alt+'
101*6dbdd20aSAndroid Build Coastguard Worker  | 'Mod+Shift+'
102*6dbdd20aSAndroid Build Coastguard Worker  | 'Mod+Alt+'
103*6dbdd20aSAndroid Build Coastguard Worker  | 'Mod+Shift+Alt+'
104*6dbdd20aSAndroid Build Coastguard Worker  | 'Ctrl+Shift+'
105*6dbdd20aSAndroid Build Coastguard Worker  | 'Ctrl+Alt'
106*6dbdd20aSAndroid Build Coastguard Worker  | 'Ctrl+Shift+Alt';
107*6dbdd20aSAndroid Build Coastguard Workertype AllowInEditable = '!' | '';
108*6dbdd20aSAndroid Build Coastguard Workerexport type Hotkey = `${AllowInEditable}${Modifier}${Key}`;
109*6dbdd20aSAndroid Build Coastguard Worker
110*6dbdd20aSAndroid Build Coastguard Worker// The following list of keys cannot be pressed wither with or without the
111*6dbdd20aSAndroid Build Coastguard Worker// presence of the Shift modifier on most keyboard layouts. Thus we should
112*6dbdd20aSAndroid Build Coastguard Worker// ignore shift in these cases.
113*6dbdd20aSAndroid Build Coastguard Workerconst shiftExceptions = [
114*6dbdd20aSAndroid Build Coastguard Worker  '0',
115*6dbdd20aSAndroid Build Coastguard Worker  '1',
116*6dbdd20aSAndroid Build Coastguard Worker  '2',
117*6dbdd20aSAndroid Build Coastguard Worker  '3',
118*6dbdd20aSAndroid Build Coastguard Worker  '4',
119*6dbdd20aSAndroid Build Coastguard Worker  '5',
120*6dbdd20aSAndroid Build Coastguard Worker  '6',
121*6dbdd20aSAndroid Build Coastguard Worker  '7',
122*6dbdd20aSAndroid Build Coastguard Worker  '8',
123*6dbdd20aSAndroid Build Coastguard Worker  '9',
124*6dbdd20aSAndroid Build Coastguard Worker  '/',
125*6dbdd20aSAndroid Build Coastguard Worker  '?',
126*6dbdd20aSAndroid Build Coastguard Worker  '!',
127*6dbdd20aSAndroid Build Coastguard Worker  '[',
128*6dbdd20aSAndroid Build Coastguard Worker  ']',
129*6dbdd20aSAndroid Build Coastguard Worker];
130*6dbdd20aSAndroid Build Coastguard Worker
131*6dbdd20aSAndroid Build Coastguard Workerconst macModifierStrings: ReadonlyMap<Modifier, string> = new Map<
132*6dbdd20aSAndroid Build Coastguard Worker  Modifier,
133*6dbdd20aSAndroid Build Coastguard Worker  string
134*6dbdd20aSAndroid Build Coastguard Worker>([
135*6dbdd20aSAndroid Build Coastguard Worker  ['', ''],
136*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+', '⌘'],
137*6dbdd20aSAndroid Build Coastguard Worker  ['Shift+', '⇧'],
138*6dbdd20aSAndroid Build Coastguard Worker  ['Ctrl+', '⌃'],
139*6dbdd20aSAndroid Build Coastguard Worker  ['Alt+', '⌥'],
140*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Shift+', '⌘⇧'],
141*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Alt+', '⌘⌥'],
142*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Shift+Alt+', '⌘⇧⌥'],
143*6dbdd20aSAndroid Build Coastguard Worker  ['Ctrl+Shift+', '⌃⇧'],
144*6dbdd20aSAndroid Build Coastguard Worker  ['Ctrl+Alt', '⌃⌥'],
145*6dbdd20aSAndroid Build Coastguard Worker  ['Ctrl+Shift+Alt', '⌃⇧⌥'],
146*6dbdd20aSAndroid Build Coastguard Worker]);
147*6dbdd20aSAndroid Build Coastguard Worker
148*6dbdd20aSAndroid Build Coastguard Workerconst pcModifierStrings: ReadonlyMap<Modifier, string> = new Map<
149*6dbdd20aSAndroid Build Coastguard Worker  Modifier,
150*6dbdd20aSAndroid Build Coastguard Worker  string
151*6dbdd20aSAndroid Build Coastguard Worker>([
152*6dbdd20aSAndroid Build Coastguard Worker  ['', ''],
153*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+', 'Ctrl+'],
154*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Shift+', 'Ctrl+Shift+'],
155*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Alt+', 'Ctrl+Alt+'],
156*6dbdd20aSAndroid Build Coastguard Worker  ['Mod+Shift+Alt+', 'Ctrl+Shift+Alt+'],
157*6dbdd20aSAndroid Build Coastguard Worker]);
158*6dbdd20aSAndroid Build Coastguard Worker
159*6dbdd20aSAndroid Build Coastguard Worker// Represents a deconstructed hotkey.
160*6dbdd20aSAndroid Build Coastguard Workerexport interface HotkeyParts {
161*6dbdd20aSAndroid Build Coastguard Worker  // The name of the primary key of this hotkey.
162*6dbdd20aSAndroid Build Coastguard Worker  key: Key;
163*6dbdd20aSAndroid Build Coastguard Worker
164*6dbdd20aSAndroid Build Coastguard Worker  // All the modifiers as one chunk. E.g. 'Mod+Shift+'.
165*6dbdd20aSAndroid Build Coastguard Worker  modifier: Modifier;
166*6dbdd20aSAndroid Build Coastguard Worker
167*6dbdd20aSAndroid Build Coastguard Worker  // Whether this hotkey should register when the event target is inside an
168*6dbdd20aSAndroid Build Coastguard Worker  // editable field.
169*6dbdd20aSAndroid Build Coastguard Worker  allowInEditable: boolean;
170*6dbdd20aSAndroid Build Coastguard Worker}
171*6dbdd20aSAndroid Build Coastguard Worker
172*6dbdd20aSAndroid Build Coastguard Worker// Deconstruct a hotkey from its string representation into its constituent
173*6dbdd20aSAndroid Build Coastguard Worker// parts.
174*6dbdd20aSAndroid Build Coastguard Workerexport function parseHotkey(hotkey: Hotkey): HotkeyParts | undefined {
175*6dbdd20aSAndroid Build Coastguard Worker  const regex = /^(!?)((?:Mod\+|Shift\+|Alt\+|Ctrl\+)*)(.*)$/;
176*6dbdd20aSAndroid Build Coastguard Worker  const result = hotkey.match(regex);
177*6dbdd20aSAndroid Build Coastguard Worker
178*6dbdd20aSAndroid Build Coastguard Worker  if (!result) {
179*6dbdd20aSAndroid Build Coastguard Worker    return undefined;
180*6dbdd20aSAndroid Build Coastguard Worker  }
181*6dbdd20aSAndroid Build Coastguard Worker
182*6dbdd20aSAndroid Build Coastguard Worker  return {
183*6dbdd20aSAndroid Build Coastguard Worker    allowInEditable: result[1] === '!',
184*6dbdd20aSAndroid Build Coastguard Worker    modifier: result[2] as Modifier,
185*6dbdd20aSAndroid Build Coastguard Worker    key: result[3] as Key,
186*6dbdd20aSAndroid Build Coastguard Worker  };
187*6dbdd20aSAndroid Build Coastguard Worker}
188*6dbdd20aSAndroid Build Coastguard Worker
189*6dbdd20aSAndroid Build Coastguard Worker// Print the hotkey in a human readable format.
190*6dbdd20aSAndroid Build Coastguard Workerexport function formatHotkey(
191*6dbdd20aSAndroid Build Coastguard Worker  hotkey: Hotkey,
192*6dbdd20aSAndroid Build Coastguard Worker  spoof?: Platform,
193*6dbdd20aSAndroid Build Coastguard Worker): string | undefined {
194*6dbdd20aSAndroid Build Coastguard Worker  const parsed = parseHotkey(hotkey);
195*6dbdd20aSAndroid Build Coastguard Worker  return parsed && formatHotkeyParts(parsed, spoof);
196*6dbdd20aSAndroid Build Coastguard Worker}
197*6dbdd20aSAndroid Build Coastguard Worker
198*6dbdd20aSAndroid Build Coastguard Workerfunction formatHotkeyParts(
199*6dbdd20aSAndroid Build Coastguard Worker  {modifier, key}: HotkeyParts,
200*6dbdd20aSAndroid Build Coastguard Worker  spoof?: Platform,
201*6dbdd20aSAndroid Build Coastguard Worker): string {
202*6dbdd20aSAndroid Build Coastguard Worker  return `${formatModifier(modifier, spoof)}${key}`;
203*6dbdd20aSAndroid Build Coastguard Worker}
204*6dbdd20aSAndroid Build Coastguard Worker
205*6dbdd20aSAndroid Build Coastguard Workerfunction formatModifier(modifier: Modifier, spoof?: Platform): string {
206*6dbdd20aSAndroid Build Coastguard Worker  const platform = spoof || getPlatform();
207*6dbdd20aSAndroid Build Coastguard Worker  const strings = platform === 'Mac' ? macModifierStrings : pcModifierStrings;
208*6dbdd20aSAndroid Build Coastguard Worker  return strings.get(modifier) ?? modifier;
209*6dbdd20aSAndroid Build Coastguard Worker}
210*6dbdd20aSAndroid Build Coastguard Worker
211*6dbdd20aSAndroid Build Coastguard Worker// Like |KeyboardEvent| but all fields apart from |key| are optional.
212*6dbdd20aSAndroid Build Coastguard Workerexport type KeyboardEventLike = Pick<KeyboardEvent, 'key'> &
213*6dbdd20aSAndroid Build Coastguard Worker  Partial<KeyboardEvent>;
214*6dbdd20aSAndroid Build Coastguard Worker
215*6dbdd20aSAndroid Build Coastguard Worker// Check whether |hotkey| is present in the keyboard event |event|.
216*6dbdd20aSAndroid Build Coastguard Workerexport function checkHotkey(
217*6dbdd20aSAndroid Build Coastguard Worker  hotkey: Hotkey,
218*6dbdd20aSAndroid Build Coastguard Worker  event: KeyboardEventLike,
219*6dbdd20aSAndroid Build Coastguard Worker  spoofPlatform?: Platform,
220*6dbdd20aSAndroid Build Coastguard Worker): boolean {
221*6dbdd20aSAndroid Build Coastguard Worker  const result = parseHotkey(hotkey);
222*6dbdd20aSAndroid Build Coastguard Worker  if (!result) {
223*6dbdd20aSAndroid Build Coastguard Worker    return false;
224*6dbdd20aSAndroid Build Coastguard Worker  }
225*6dbdd20aSAndroid Build Coastguard Worker
226*6dbdd20aSAndroid Build Coastguard Worker  const {key, allowInEditable} = result;
227*6dbdd20aSAndroid Build Coastguard Worker  const {target = null} = event;
228*6dbdd20aSAndroid Build Coastguard Worker
229*6dbdd20aSAndroid Build Coastguard Worker  const inEditable = elementIsEditable(target);
230*6dbdd20aSAndroid Build Coastguard Worker  if (inEditable && !allowInEditable) {
231*6dbdd20aSAndroid Build Coastguard Worker    return false;
232*6dbdd20aSAndroid Build Coastguard Worker  }
233*6dbdd20aSAndroid Build Coastguard Worker  return compareKeys(event, key) && checkMods(event, result, spoofPlatform);
234*6dbdd20aSAndroid Build Coastguard Worker}
235*6dbdd20aSAndroid Build Coastguard Worker
236*6dbdd20aSAndroid Build Coastguard Worker// Return true if |key| matches the event's key.
237*6dbdd20aSAndroid Build Coastguard Workerfunction compareKeys(e: KeyboardEventLike, key: Key): boolean {
238*6dbdd20aSAndroid Build Coastguard Worker  return e.key.toLowerCase() === key.toLowerCase();
239*6dbdd20aSAndroid Build Coastguard Worker}
240*6dbdd20aSAndroid Build Coastguard Worker
241*6dbdd20aSAndroid Build Coastguard Worker// Return true if modifiers specified in |mods| match those in the event.
242*6dbdd20aSAndroid Build Coastguard Workerfunction checkMods(
243*6dbdd20aSAndroid Build Coastguard Worker  event: KeyboardEventLike,
244*6dbdd20aSAndroid Build Coastguard Worker  hotkey: HotkeyParts,
245*6dbdd20aSAndroid Build Coastguard Worker  spoofPlatform?: Platform,
246*6dbdd20aSAndroid Build Coastguard Worker): boolean {
247*6dbdd20aSAndroid Build Coastguard Worker  const platform = spoofPlatform ?? getPlatform();
248*6dbdd20aSAndroid Build Coastguard Worker
249*6dbdd20aSAndroid Build Coastguard Worker  const {key, modifier} = hotkey;
250*6dbdd20aSAndroid Build Coastguard Worker
251*6dbdd20aSAndroid Build Coastguard Worker  const {
252*6dbdd20aSAndroid Build Coastguard Worker    ctrlKey = false,
253*6dbdd20aSAndroid Build Coastguard Worker    altKey = false,
254*6dbdd20aSAndroid Build Coastguard Worker    shiftKey = false,
255*6dbdd20aSAndroid Build Coastguard Worker    metaKey = false,
256*6dbdd20aSAndroid Build Coastguard Worker  } = event;
257*6dbdd20aSAndroid Build Coastguard Worker
258*6dbdd20aSAndroid Build Coastguard Worker  const wantShift = modifier.includes('Shift');
259*6dbdd20aSAndroid Build Coastguard Worker  const wantAlt = modifier.includes('Alt');
260*6dbdd20aSAndroid Build Coastguard Worker  const wantCtrl =
261*6dbdd20aSAndroid Build Coastguard Worker    platform === 'Mac'
262*6dbdd20aSAndroid Build Coastguard Worker      ? modifier.includes('Ctrl')
263*6dbdd20aSAndroid Build Coastguard Worker      : modifier.includes('Ctrl') || modifier.includes('Mod');
264*6dbdd20aSAndroid Build Coastguard Worker  const wantMeta = platform === 'Mac' && modifier.includes('Mod');
265*6dbdd20aSAndroid Build Coastguard Worker
266*6dbdd20aSAndroid Build Coastguard Worker  // For certain keys we relax the shift requirement, as they usually cannot be
267*6dbdd20aSAndroid Build Coastguard Worker  // pressed without the shift key on English keyboards.
268*6dbdd20aSAndroid Build Coastguard Worker  const shiftOk =
269*6dbdd20aSAndroid Build Coastguard Worker    shiftExceptions.includes(key as string) || shiftKey === wantShift;
270*6dbdd20aSAndroid Build Coastguard Worker
271*6dbdd20aSAndroid Build Coastguard Worker  return (
272*6dbdd20aSAndroid Build Coastguard Worker    metaKey === wantMeta &&
273*6dbdd20aSAndroid Build Coastguard Worker    Boolean(shiftOk) &&
274*6dbdd20aSAndroid Build Coastguard Worker    altKey === wantAlt &&
275*6dbdd20aSAndroid Build Coastguard Worker    ctrlKey === wantCtrl
276*6dbdd20aSAndroid Build Coastguard Worker  );
277*6dbdd20aSAndroid Build Coastguard Worker}
278*6dbdd20aSAndroid Build Coastguard Worker
279*6dbdd20aSAndroid Build Coastguard Workerexport type Platform = 'Mac' | 'PC';
280*6dbdd20aSAndroid Build Coastguard Worker
281*6dbdd20aSAndroid Build Coastguard Worker// Get the current platform (PC or Mac).
282*6dbdd20aSAndroid Build Coastguard Workerexport function getPlatform(): Platform {
283*6dbdd20aSAndroid Build Coastguard Worker  return window.navigator.platform.indexOf('Mac') !== -1 ? 'Mac' : 'PC';
284*6dbdd20aSAndroid Build Coastguard Worker}
285*6dbdd20aSAndroid Build Coastguard Worker
286*6dbdd20aSAndroid Build Coastguard Worker// Returns a cross-platform check for whether the event has "Mod" key pressed
287*6dbdd20aSAndroid Build Coastguard Worker// (e.g. as a part of Mod-Click UX pattern).
288*6dbdd20aSAndroid Build Coastguard Worker// On Mac, Mod-click is actually Command-click and on PC it's Control-click,
289*6dbdd20aSAndroid Build Coastguard Worker// so this function handles this for all platforms.
290*6dbdd20aSAndroid Build Coastguard Workerexport function hasModKey(event: {
291*6dbdd20aSAndroid Build Coastguard Worker  readonly metaKey: boolean;
292*6dbdd20aSAndroid Build Coastguard Worker  readonly ctrlKey: boolean;
293*6dbdd20aSAndroid Build Coastguard Worker}): boolean {
294*6dbdd20aSAndroid Build Coastguard Worker  if (getPlatform() === 'Mac') {
295*6dbdd20aSAndroid Build Coastguard Worker    return event.metaKey;
296*6dbdd20aSAndroid Build Coastguard Worker  } else {
297*6dbdd20aSAndroid Build Coastguard Worker    return event.ctrlKey;
298*6dbdd20aSAndroid Build Coastguard Worker  }
299*6dbdd20aSAndroid Build Coastguard Worker}
300*6dbdd20aSAndroid Build Coastguard Worker
301*6dbdd20aSAndroid Build Coastguard Workerexport function modKey(): {metaKey?: boolean; ctrlKey?: boolean} {
302*6dbdd20aSAndroid Build Coastguard Worker  if (getPlatform() === 'Mac') {
303*6dbdd20aSAndroid Build Coastguard Worker    return {metaKey: true};
304*6dbdd20aSAndroid Build Coastguard Worker  } else {
305*6dbdd20aSAndroid Build Coastguard Worker    return {ctrlKey: true};
306*6dbdd20aSAndroid Build Coastguard Worker  }
307*6dbdd20aSAndroid Build Coastguard Worker}
308