generated from gxc-solutions/gxc-template-repo
Added files
This commit is contained in:
parent
bf0142bd6a
commit
7c351972cc
17 changed files with 966 additions and 8 deletions
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: gxc-math-${{ github.sha }}
|
name: gxc-hotkeys-${{ github.sha }}
|
||||||
path: ./dist/
|
path: ./dist/
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|
@ -53,7 +53,7 @@ jobs:
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: gxc-math-${{ github.sha }}
|
name: gxc-hotkeys-${{ github.sha }}
|
||||||
path: ./artifact
|
path: ./artifact
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
/** Identifier for global keyboard zone */
|
||||||
|
export const GLOBAL_ZONE_TOKEN = "GLOBAL";
|
||||||
205
lib/src/constants/key-labels.ts
Normal file
205
lib/src/constants/key-labels.ts
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { IKeyLabel } from "../interfaces";
|
||||||
|
import { Key } from "./key";
|
||||||
|
|
||||||
|
/** Labels for Windows keys */
|
||||||
|
export const WINDOWS_KEYS: IKeyLabel[] = [
|
||||||
|
{ code: Key.Backspace, label: "Backspace" },
|
||||||
|
{ code: Key.Tab, label: "Tab" },
|
||||||
|
{ code: Key.Enter, label: "Enter" },
|
||||||
|
{ code: Key.Shift, label: "Shift" },
|
||||||
|
{ code: Key.Ctrl, label: "Ctrl" },
|
||||||
|
{ code: Key.Alt, label: "Alt" },
|
||||||
|
{ code: Key.PauseBreak, label: "Pause" },
|
||||||
|
{ code: Key.CapsLock, label: "Caps Lock" },
|
||||||
|
{ code: Key.Escape, label: "Esc" },
|
||||||
|
{ code: Key.Space, label: "Space" },
|
||||||
|
{ code: Key.PageUp, label: "PgUp" },
|
||||||
|
{ code: Key.PageDown, label: "PgDn" },
|
||||||
|
{ code: Key.End, label: "End" },
|
||||||
|
{ code: Key.Home, label: "Home" },
|
||||||
|
{ code: Key.LeftArrow, label: "←" },
|
||||||
|
{ code: Key.UpArrow, label: "↑" },
|
||||||
|
{ code: Key.RightArrow, label: "→" },
|
||||||
|
{ code: Key.DownArrow, label: "↓" },
|
||||||
|
{ code: Key.Insert, label: "Insert" },
|
||||||
|
{ code: Key.Delete, label: "Del" },
|
||||||
|
{ code: Key.Zero, label: "0", altLabel: ")" },
|
||||||
|
{ code: Key.One, label: "1", altLabel: "!" },
|
||||||
|
{ code: Key.Two, label: "2", altLabel: "@" },
|
||||||
|
{ code: Key.Three, label: "3", altLabel: "#" },
|
||||||
|
{ code: Key.Four, label: "4", altLabel: "$" },
|
||||||
|
{ code: Key.Five, label: "5", altLabel: "%" },
|
||||||
|
{ code: Key.Six, label: "6", altLabel: "^" },
|
||||||
|
{ code: Key.Seven, label: "7", altLabel: "&" },
|
||||||
|
{ code: Key.Eight, label: "8", altLabel: "*" },
|
||||||
|
{ code: Key.Nine, label: "9", altLabel: "(" },
|
||||||
|
{ code: Key.A, label: "A" },
|
||||||
|
{ code: Key.B, label: "B" },
|
||||||
|
{ code: Key.C, label: "C" },
|
||||||
|
{ code: Key.D, label: "D" },
|
||||||
|
{ code: Key.E, label: "E" },
|
||||||
|
{ code: Key.F, label: "F" },
|
||||||
|
{ code: Key.G, label: "G" },
|
||||||
|
{ code: Key.H, label: "H" },
|
||||||
|
{ code: Key.I, label: "I" },
|
||||||
|
{ code: Key.J, label: "J" },
|
||||||
|
{ code: Key.K, label: "K" },
|
||||||
|
{ code: Key.L, label: "L" },
|
||||||
|
{ code: Key.M, label: "M" },
|
||||||
|
{ code: Key.N, label: "N" },
|
||||||
|
{ code: Key.O, label: "O" },
|
||||||
|
{ code: Key.P, label: "P" },
|
||||||
|
{ code: Key.Q, label: "Q" },
|
||||||
|
{ code: Key.R, label: "R" },
|
||||||
|
{ code: Key.S, label: "S" },
|
||||||
|
{ code: Key.T, label: "T" },
|
||||||
|
{ code: Key.U, label: "U" },
|
||||||
|
{ code: Key.V, label: "V" },
|
||||||
|
{ code: Key.W, label: "W" },
|
||||||
|
{ code: Key.X, label: "X" },
|
||||||
|
{ code: Key.Y, label: "Y" },
|
||||||
|
{ code: Key.Z, label: "Z" },
|
||||||
|
{ code: Key.LeftWindowKey, label: "Win" },
|
||||||
|
{ code: Key.RightWindowKey, label: "Win" },
|
||||||
|
{ code: Key.SelectKey, label: "Menu" },
|
||||||
|
{ code: Key.Numpad0, label: "0" },
|
||||||
|
{ code: Key.Numpad1, label: "1" },
|
||||||
|
{ code: Key.Numpad2, label: "2" },
|
||||||
|
{ code: Key.Numpad3, label: "3" },
|
||||||
|
{ code: Key.Numpad4, label: "4" },
|
||||||
|
{ code: Key.Numpad5, label: "5" },
|
||||||
|
{ code: Key.Numpad6, label: "6" },
|
||||||
|
{ code: Key.Numpad7, label: "7" },
|
||||||
|
{ code: Key.Numpad8, label: "8" },
|
||||||
|
{ code: Key.Numpad9, label: "9" },
|
||||||
|
{ code: Key.Multiply, label: "*" },
|
||||||
|
{ code: Key.Add, label: "+" },
|
||||||
|
{ code: Key.Subtract, label: "-" },
|
||||||
|
{ code: Key.DecimalPoint, label: "." },
|
||||||
|
{ code: Key.Divide, label: "/" },
|
||||||
|
{ code: Key.F1, label: "F1" },
|
||||||
|
{ code: Key.F2, label: "F2" },
|
||||||
|
{ code: Key.F3, label: "F3" },
|
||||||
|
{ code: Key.F4, label: "F4" },
|
||||||
|
{ code: Key.F5, label: "F5" },
|
||||||
|
{ code: Key.F6, label: "F6" },
|
||||||
|
{ code: Key.F7, label: "F7" },
|
||||||
|
{ code: Key.F8, label: "F8" },
|
||||||
|
{ code: Key.F9, label: "F9" },
|
||||||
|
{ code: Key.F10, label: "F10" },
|
||||||
|
{ code: Key.F11, label: "F11" },
|
||||||
|
{ code: Key.F12, label: "F12" },
|
||||||
|
{ code: Key.NumLock, label: "Num Lock" },
|
||||||
|
{ code: Key.ScrollLock, label: "ScrLk" },
|
||||||
|
{ code: Key.SemiColon, label: ";" },
|
||||||
|
{ code: Key.Equals, label: "=", altLabel: "+" },
|
||||||
|
{ code: Key.Comma, label: "," },
|
||||||
|
{ code: Key.Dash, label: "-", altLabel: "_" },
|
||||||
|
{ code: Key.Period, label: "." },
|
||||||
|
{ code: Key.ForwardSlash, label: "/" },
|
||||||
|
{ code: Key.Tilde, label: "~", altLabel: "`" },
|
||||||
|
{ code: Key.OpenBracket, label: "[" },
|
||||||
|
{ code: Key.ClosedBracket, label: "]" },
|
||||||
|
{ code: Key.Quote, label: "'" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Labels for Mac OS keys */
|
||||||
|
export const MAC_KEYS: IKeyLabel[] = [
|
||||||
|
{ code: Key.Backspace, label: "Backspace" },
|
||||||
|
{ code: Key.Tab, label: "⇥" },
|
||||||
|
{ code: Key.Enter, label: "Enter" },
|
||||||
|
{ code: Key.Shift, label: "⇧" },
|
||||||
|
{ code: Key.Ctrl, label: "Control" },
|
||||||
|
{ code: Key.Alt, label: "⌥" },
|
||||||
|
{ code: Key.PauseBreak, label: "Pause" },
|
||||||
|
{ code: Key.CapsLock, label: "⇪" },
|
||||||
|
{ code: Key.Escape, label: "Esc" },
|
||||||
|
{ code: Key.Space, label: "Space" },
|
||||||
|
{ code: Key.PageUp, label: "PgUp" },
|
||||||
|
{ code: Key.PageDown, label: "PgDn" },
|
||||||
|
{ code: Key.End, label: "End" },
|
||||||
|
{ code: Key.Home, label: "Home" },
|
||||||
|
{ code: Key.LeftArrow, label: "←" },
|
||||||
|
{ code: Key.UpArrow, label: "↑" },
|
||||||
|
{ code: Key.RightArrow, label: "→" },
|
||||||
|
{ code: Key.DownArrow, label: "↓" },
|
||||||
|
{ code: Key.Insert, label: "Insert" },
|
||||||
|
{ code: Key.Delete, label: "Backspace" },
|
||||||
|
{ code: Key.Zero, label: "0", altLabel: ")" },
|
||||||
|
{ code: Key.One, label: "1", altLabel: "!" },
|
||||||
|
{ code: Key.Two, label: "2", altLabel: "@" },
|
||||||
|
{ code: Key.Three, label: "3", altLabel: "#" },
|
||||||
|
{ code: Key.Four, label: "4", altLabel: "$" },
|
||||||
|
{ code: Key.Five, label: "5", altLabel: "%" },
|
||||||
|
{ code: Key.Six, label: "6", altLabel: "^" },
|
||||||
|
{ code: Key.Seven, label: "7", altLabel: "&" },
|
||||||
|
{ code: Key.Eight, label: "8", altLabel: "*" },
|
||||||
|
{ code: Key.Nine, label: "9", altLabel: "(" },
|
||||||
|
{ code: Key.A, label: "A" },
|
||||||
|
{ code: Key.B, label: "B" },
|
||||||
|
{ code: Key.C, label: "C" },
|
||||||
|
{ code: Key.D, label: "D" },
|
||||||
|
{ code: Key.E, label: "E" },
|
||||||
|
{ code: Key.F, label: "F" },
|
||||||
|
{ code: Key.G, label: "G" },
|
||||||
|
{ code: Key.H, label: "H" },
|
||||||
|
{ code: Key.I, label: "I" },
|
||||||
|
{ code: Key.J, label: "J" },
|
||||||
|
{ code: Key.K, label: "K" },
|
||||||
|
{ code: Key.L, label: "L" },
|
||||||
|
{ code: Key.M, label: "M" },
|
||||||
|
{ code: Key.N, label: "N" },
|
||||||
|
{ code: Key.O, label: "O" },
|
||||||
|
{ code: Key.P, label: "P" },
|
||||||
|
{ code: Key.Q, label: "Q" },
|
||||||
|
{ code: Key.R, label: "R" },
|
||||||
|
{ code: Key.S, label: "S" },
|
||||||
|
{ code: Key.T, label: "T" },
|
||||||
|
{ code: Key.U, label: "U" },
|
||||||
|
{ code: Key.V, label: "V" },
|
||||||
|
{ code: Key.W, label: "W" },
|
||||||
|
{ code: Key.X, label: "X" },
|
||||||
|
{ code: Key.Y, label: "Y" },
|
||||||
|
{ code: Key.Z, label: "Z" },
|
||||||
|
{ code: Key.LeftWindowKey, label: "⌘" },
|
||||||
|
{ code: Key.RightWindowKey, label: "⌘" },
|
||||||
|
{ code: Key.Numpad0, label: "0" },
|
||||||
|
{ code: Key.Numpad1, label: "1" },
|
||||||
|
{ code: Key.Numpad2, label: "2" },
|
||||||
|
{ code: Key.Numpad3, label: "3" },
|
||||||
|
{ code: Key.Numpad4, label: "4" },
|
||||||
|
{ code: Key.Numpad5, label: "5" },
|
||||||
|
{ code: Key.Numpad6, label: "6" },
|
||||||
|
{ code: Key.Numpad7, label: "7" },
|
||||||
|
{ code: Key.Numpad8, label: "8" },
|
||||||
|
{ code: Key.Numpad9, label: "9" },
|
||||||
|
{ code: Key.Multiply, label: "*" },
|
||||||
|
{ code: Key.Add, label: "+" },
|
||||||
|
{ code: Key.Subtract, label: "-" },
|
||||||
|
{ code: Key.DecimalPoint, label: "." },
|
||||||
|
{ code: Key.Divide, label: "/" },
|
||||||
|
{ code: Key.F1, label: "F1" },
|
||||||
|
{ code: Key.F2, label: "F2" },
|
||||||
|
{ code: Key.F3, label: "F3" },
|
||||||
|
{ code: Key.F4, label: "F4" },
|
||||||
|
{ code: Key.F5, label: "F5" },
|
||||||
|
{ code: Key.F6, label: "F6" },
|
||||||
|
{ code: Key.F7, label: "F7" },
|
||||||
|
{ code: Key.F8, label: "F8" },
|
||||||
|
{ code: Key.F9, label: "F9" },
|
||||||
|
{ code: Key.F10, label: "F10" },
|
||||||
|
{ code: Key.F11, label: "F11" },
|
||||||
|
{ code: Key.F12, label: "F12" },
|
||||||
|
{ code: Key.NumLock, label: "Num Lock" },
|
||||||
|
{ code: Key.ScrollLock, label: "ScrLk" },
|
||||||
|
{ code: Key.SemiColon, label: ";" },
|
||||||
|
{ code: Key.Equals, label: "=", altLabel: "+" },
|
||||||
|
{ code: Key.Comma, label: "," },
|
||||||
|
{ code: Key.Dash, label: "-", altLabel: "_" },
|
||||||
|
{ code: Key.Period, label: "." },
|
||||||
|
{ code: Key.ForwardSlash, label: "/" },
|
||||||
|
{ code: Key.Tilde, label: "~", altLabel: "`" },
|
||||||
|
{ code: Key.OpenBracket, label: "[" },
|
||||||
|
{ code: Key.ClosedBracket, label: "]" },
|
||||||
|
{ code: Key.Quote, label: "'" },
|
||||||
|
];
|
||||||
127
lib/src/constants/key.ts
Normal file
127
lib/src/constants/key.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
export enum Key {
|
||||||
|
Backspace = 8,
|
||||||
|
Tab = 9,
|
||||||
|
Enter = 13,
|
||||||
|
Shift = 16,
|
||||||
|
Ctrl = 17,
|
||||||
|
Alt = 18,
|
||||||
|
PauseBreak = 19,
|
||||||
|
CapsLock = 20,
|
||||||
|
Escape = 27,
|
||||||
|
Space = 32,
|
||||||
|
PageUp = 33,
|
||||||
|
PageDown = 34,
|
||||||
|
End = 35,
|
||||||
|
Home = 36,
|
||||||
|
|
||||||
|
LeftArrow = 37,
|
||||||
|
UpArrow = 38,
|
||||||
|
RightArrow = 39,
|
||||||
|
DownArrow = 40,
|
||||||
|
|
||||||
|
Insert = 45,
|
||||||
|
Delete = 46,
|
||||||
|
|
||||||
|
Zero = 48,
|
||||||
|
ClosedParen = Zero,
|
||||||
|
One = 49,
|
||||||
|
ExclamationMark = One,
|
||||||
|
Two = 50,
|
||||||
|
AtSign = Two,
|
||||||
|
Three = 51,
|
||||||
|
PoundSign = Three,
|
||||||
|
Hash = PoundSign,
|
||||||
|
Four = 52,
|
||||||
|
DollarSign = Four,
|
||||||
|
Five = 53,
|
||||||
|
PercentSign = Five,
|
||||||
|
Six = 54,
|
||||||
|
Caret = Six,
|
||||||
|
Hat = Caret,
|
||||||
|
Seven = 55,
|
||||||
|
Ampersand = Seven,
|
||||||
|
Eight = 56,
|
||||||
|
Star = Eight,
|
||||||
|
Asterik = Star,
|
||||||
|
Nine = 57,
|
||||||
|
OpenParen = Nine,
|
||||||
|
|
||||||
|
A = 65,
|
||||||
|
B = 66,
|
||||||
|
C = 67,
|
||||||
|
D = 68,
|
||||||
|
E = 69,
|
||||||
|
F = 70,
|
||||||
|
G = 71,
|
||||||
|
H = 72,
|
||||||
|
I = 73,
|
||||||
|
J = 74,
|
||||||
|
K = 75,
|
||||||
|
L = 76,
|
||||||
|
M = 77,
|
||||||
|
N = 78,
|
||||||
|
O = 79,
|
||||||
|
P = 80,
|
||||||
|
Q = 81,
|
||||||
|
R = 82,
|
||||||
|
S = 83,
|
||||||
|
T = 84,
|
||||||
|
U = 85,
|
||||||
|
V = 86,
|
||||||
|
W = 87,
|
||||||
|
X = 88,
|
||||||
|
Y = 89,
|
||||||
|
Z = 90,
|
||||||
|
|
||||||
|
LeftWindowKey = 91,
|
||||||
|
RightWindowKey = 92,
|
||||||
|
SelectKey = 93,
|
||||||
|
|
||||||
|
Numpad0 = 96,
|
||||||
|
Numpad1 = 97,
|
||||||
|
Numpad2 = 98,
|
||||||
|
Numpad3 = 99,
|
||||||
|
Numpad4 = 100,
|
||||||
|
Numpad5 = 101,
|
||||||
|
Numpad6 = 102,
|
||||||
|
Numpad7 = 103,
|
||||||
|
Numpad8 = 104,
|
||||||
|
Numpad9 = 105,
|
||||||
|
|
||||||
|
Multiply = 106,
|
||||||
|
Add = 107,
|
||||||
|
Subtract = 109,
|
||||||
|
DecimalPoint = 110,
|
||||||
|
Divide = 111,
|
||||||
|
|
||||||
|
F1 = 112,
|
||||||
|
F2 = 113,
|
||||||
|
F3 = 114,
|
||||||
|
F4 = 115,
|
||||||
|
F5 = 116,
|
||||||
|
F6 = 117,
|
||||||
|
F7 = 118,
|
||||||
|
F8 = 119,
|
||||||
|
F9 = 120,
|
||||||
|
F10 = 121,
|
||||||
|
F11 = 122,
|
||||||
|
F12 = 123,
|
||||||
|
|
||||||
|
NumLock = 144,
|
||||||
|
ScrollLock = 145,
|
||||||
|
|
||||||
|
SemiColon = 186,
|
||||||
|
Equals = 187,
|
||||||
|
Comma = 188,
|
||||||
|
Dash = 189,
|
||||||
|
Period = 190,
|
||||||
|
UnderScore = Dash,
|
||||||
|
PlusSign = Equals,
|
||||||
|
ForwardSlash = 191,
|
||||||
|
Tilde = 192,
|
||||||
|
GraveAccent = Tilde,
|
||||||
|
|
||||||
|
OpenBracket = 219,
|
||||||
|
ClosedBracket = 221,
|
||||||
|
Quote = 222,
|
||||||
|
}
|
||||||
18
lib/src/functions/index.spec.ts
Normal file
18
lib/src/functions/index.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { adaptKeysToPlatform, getOS } from ".";
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
import { MAC_SPECIAL_KEYS, OPERATING_SYSTEM } from "../interfaces";
|
||||||
|
|
||||||
|
describe("Utils", () => {
|
||||||
|
beforeEach(async () => {});
|
||||||
|
|
||||||
|
it("Adapting keys to platform", () => {
|
||||||
|
const os = getOS();
|
||||||
|
const adapted = adaptKeysToPlatform([[Key.Ctrl]]);
|
||||||
|
if (os === OPERATING_SYSTEM.WINDOWS) {
|
||||||
|
expect(adapted[0][0]).toBe(Key.Ctrl);
|
||||||
|
}
|
||||||
|
if (os === OPERATING_SYSTEM.MAC_OS) {
|
||||||
|
expect(adapted[0][0]).toBe(MAC_SPECIAL_KEYS.CmdLeft as any);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
70
lib/src/functions/index.ts
Normal file
70
lib/src/functions/index.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
import { MAC_KEYS, WINDOWS_KEYS } from "../constants/key-labels";
|
||||||
|
import { IKeyLabel, IKeyRegistry, OPERATING_SYSTEM } from "../interfaces";
|
||||||
|
import { getMacKeys } from "../utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create label string for hotkey depending on OS
|
||||||
|
* @param keySet - array describing the hotkey. For example: [Key.Ctrl, Key.A]
|
||||||
|
* @param delimiter - string for joining labels
|
||||||
|
* @returns label string for hotkey. For example "Ctrl + A"
|
||||||
|
*/
|
||||||
|
export const getLabelByKeySet = (keySet: Key[], delimiter?: string): string => {
|
||||||
|
let platformKeySet: IKeyLabel[] = WINDOWS_KEYS;
|
||||||
|
|
||||||
|
if (getOS() === OPERATING_SYSTEM.MAC_OS) {
|
||||||
|
if (delimiter == null) delimiter = "";
|
||||||
|
platformKeySet = MAC_KEYS;
|
||||||
|
} else {
|
||||||
|
if (delimiter == null) delimiter = " + ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(keySet)) {
|
||||||
|
return keySet
|
||||||
|
.map((key) => platformKeySet.find((item) => item.code === key)?.label ?? "")
|
||||||
|
.filter((label) => label !== "")
|
||||||
|
.join(delimiter);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create label string for hotkey depending on OS using interface `IKeyRegistry`.
|
||||||
|
* For creating label will be use first hotkey from `keys` array
|
||||||
|
*/
|
||||||
|
export const getLabel = (hotkey: IKeyRegistry, delimiter?: string): string => hotkey.label ?? getLabelByKeySet(hotkey.keys[0], delimiter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt keyboard keys to platform
|
||||||
|
* @param keySet - array describing the hotkey. For example: [Key.Ctrl, Key.A]
|
||||||
|
* @returns keyboard keys adapted to the platform
|
||||||
|
*/
|
||||||
|
export const adaptKeysToPlatform = (keySet: Key[][]): Key[][] => {
|
||||||
|
switch (getOS()) {
|
||||||
|
case OPERATING_SYSTEM.WINDOWS:
|
||||||
|
case OPERATING_SYSTEM.OTHER:
|
||||||
|
return keySet;
|
||||||
|
case OPERATING_SYSTEM.MAC_OS:
|
||||||
|
return getMacKeys(keySet);
|
||||||
|
default:
|
||||||
|
return keySet;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get current OS */
|
||||||
|
export const getOS = () => {
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i;
|
||||||
|
const windowsPlatforms = /(win32|win64|windows|wince)/i;
|
||||||
|
const iosPlatforms = /(iphone|ipad|ipod)/i;
|
||||||
|
|
||||||
|
if (macosPlatforms.test(userAgent)) {
|
||||||
|
return OPERATING_SYSTEM.MAC_OS;
|
||||||
|
} else if (windowsPlatforms.test(userAgent)) {
|
||||||
|
return OPERATING_SYSTEM.WINDOWS;
|
||||||
|
} else if (iosPlatforms.test(userAgent)) {
|
||||||
|
return OPERATING_SYSTEM.IOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OPERATING_SYSTEM.OTHER;
|
||||||
|
};
|
||||||
308
lib/src/hotkey-manager.ts
Normal file
308
lib/src/hotkey-manager.ts
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
import { arraysIsEqual } from "@gxc-solutions/lett-js/algorithms/array";
|
||||||
|
import { Subject, fromEvent, takeWhile } from "rxjs";
|
||||||
|
import { GLOBAL_ZONE_TOKEN } from "./constants";
|
||||||
|
import { Key } from "./constants/key";
|
||||||
|
import { FindKeyCb, IKeyRegistry, IKeyboardEvent, IRegistrationCheckResult, MouseKey } from "./interfaces";
|
||||||
|
import { KeyRegistry } from "./models/key-registry";
|
||||||
|
import { actionExistError, correspondsToKeySet, hotkeyExistError } from "./utils";
|
||||||
|
|
||||||
|
/** A service that registers hotkeys, handles events, and notifies of hotkey events */
|
||||||
|
export class HotkeyManager<A = string> {
|
||||||
|
protected _zonesMap = new Map<string, Map<A, KeyRegistry<A>>>([[GLOBAL_ZONE_TOKEN, new Map()]]);
|
||||||
|
protected _events = new Subject<IKeyboardEvent<A>>();
|
||||||
|
/** Current pressed keyboard key */
|
||||||
|
protected _currentDownKeys: Key[] = [];
|
||||||
|
/** Current pressed mouse buttons */
|
||||||
|
protected _currentMouseDownKeys = new Set<MouseKey>([]);
|
||||||
|
protected _isDestroyed = false;
|
||||||
|
/** Set `true` for ignore events */
|
||||||
|
public ignore = false;
|
||||||
|
|
||||||
|
/** Notify when found hotkeys event */
|
||||||
|
get $keyboardEvents() {
|
||||||
|
return this._events.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hideconstructor */
|
||||||
|
constructor(
|
||||||
|
private _element: HTMLElement,
|
||||||
|
rootHotkeys: IKeyRegistry<A>[],
|
||||||
|
) {
|
||||||
|
// Keyboard
|
||||||
|
fromEvent(this._element, "keydown")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: KeyboardEvent) => this.handleKeyPressing(e));
|
||||||
|
fromEvent(this._element, "keyup")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: KeyboardEvent) => this.handleKeyUp(e));
|
||||||
|
// Mouse
|
||||||
|
fromEvent(this._element, "blur")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe(() => (this._currentDownKeys = []));
|
||||||
|
fromEvent(this._element, "wheel", { passive: false })
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: WheelEvent) => this.handleWheel(e));
|
||||||
|
fromEvent(this._element, "click")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: MouseEvent) => this.handleClick(e));
|
||||||
|
fromEvent(this._element, "dbclick")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: MouseEvent) => this.handleDbClick(e));
|
||||||
|
// fromEvent(window, "mousemove").subscribe((e: MouseEvent) => this._handleMove(e));
|
||||||
|
fromEvent(this._element, "mousedown")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: MouseEvent) => this.handleMouseDown(e));
|
||||||
|
fromEvent(this._element, "mouseup")
|
||||||
|
.pipe(takeWhile(() => !this._isDestroyed))
|
||||||
|
.subscribe((e: MouseEvent) => this.handleMouseUp(e));
|
||||||
|
// Registry root hotkeys
|
||||||
|
this.registryKeysSet(rootHotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hotkeys to system
|
||||||
|
* @param keyInfo - hotkey registry data
|
||||||
|
* @param provider - handler provider service
|
||||||
|
*/
|
||||||
|
public registryKeys(keyInfo: IKeyRegistry<A>): void {
|
||||||
|
const keyRegistry = new KeyRegistry<A>(keyInfo);
|
||||||
|
const map = this._zonesMap.get(keyRegistry.zone);
|
||||||
|
const result = this._isRegisteredHotkey(keyRegistry);
|
||||||
|
if (result.isRegistered) {
|
||||||
|
throw new Error(result.message);
|
||||||
|
}
|
||||||
|
if (map) {
|
||||||
|
map.set(keyRegistry.action, keyRegistry);
|
||||||
|
} else {
|
||||||
|
const actionMap = new Map<A, KeyRegistry<A>>();
|
||||||
|
this._zonesMap.set(keyRegistry.zone, actionMap);
|
||||||
|
actionMap.set(keyRegistry.action, keyRegistry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister hotkey from system
|
||||||
|
* @param keyInfo - hotkey registry data
|
||||||
|
*/
|
||||||
|
public unregisterKeys(keyInfo: IKeyRegistry<A>): void {
|
||||||
|
this._zonesMap.get(keyInfo.zone || GLOBAL_ZONE_TOKEN)?.delete(keyInfo.action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk hotkeys registration
|
||||||
|
* @param keysSet - array hotkey registry data
|
||||||
|
* @param provider - handler provider service
|
||||||
|
*/
|
||||||
|
public registryKeysSet(keysSet: IKeyRegistry<A>[]): void {
|
||||||
|
keysSet.forEach((item) => this.registryKeys(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk hotkeys unregister
|
||||||
|
* @param keysSet - array hotkey registry data
|
||||||
|
*/
|
||||||
|
public unregisterKeysSet(keysSet: IKeyRegistry<A>[]): void {
|
||||||
|
keysSet.forEach((item) => this.unregisterKeys(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Found hotkey event by current pressed keys and other events
|
||||||
|
* @param zone - zone for find
|
||||||
|
* @returns hotkey event
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
public getEventByKeys(zone: string, event: Event, cb: FindKeyCb): IKeyboardEvent<A> | undefined {
|
||||||
|
const [action, inZone] = this._findHotkeyAction(zone, cb);
|
||||||
|
if (action) {
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
keys: [...this._currentDownKeys],
|
||||||
|
zone: inZone,
|
||||||
|
triggeringEvent: event,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle key press event and try found registered hotkey event
|
||||||
|
* @param zone - the zone from which the event came
|
||||||
|
* @returns found hotkeys event (if found)
|
||||||
|
*/
|
||||||
|
public handleKeyPressing(e: KeyboardEvent, zone: string = GLOBAL_ZONE_TOKEN): IKeyboardEvent<A> | undefined {
|
||||||
|
if (this._isPressedKey(e.keyCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._currentDownKeys.push(Key[Key[e.keyCode]]);
|
||||||
|
const event = this.getEventByKeys(zone, e, (k, m, mouseKeys) => this._isCurrentKeySet(k) && !(mouseKeys && mouseKeys?.length) && !m);
|
||||||
|
this._notify(e, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notify(e: Event, event: IKeyboardEvent<A>): void {
|
||||||
|
if (event && !this.ignore) {
|
||||||
|
e.preventDefault();
|
||||||
|
this._events.next(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove key from list current down keys */
|
||||||
|
public handleKeyUp(e: KeyboardEvent): void {
|
||||||
|
this._currentDownKeys = this._currentDownKeys.filter((key) => key !== Key[Key[e.keyCode]]);
|
||||||
|
if ((e as any).key === "Meta") {
|
||||||
|
this.clearCurrentDownKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle wheel event and try found registered hotkey event
|
||||||
|
* @param zone - the zone from which the event came
|
||||||
|
* @returns found hotkeys event (if found)
|
||||||
|
*/
|
||||||
|
public handleWheel(e: WheelEvent, zone: string = GLOBAL_ZONE_TOKEN): IKeyboardEvent<A> {
|
||||||
|
let event: IKeyboardEvent<A>;
|
||||||
|
if (e.deltaY < 0) {
|
||||||
|
event = this.getEventByKeys(
|
||||||
|
zone,
|
||||||
|
e,
|
||||||
|
(keys, mouseEvents, mouseKeys) => this._isCurrentKeySet(keys) && this._isCurMouseKeySet(mouseKeys) && mouseEvents === "wheelup",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
event = this.getEventByKeys(
|
||||||
|
zone,
|
||||||
|
e,
|
||||||
|
(keys, mouseEvents, mouseKeys) => this._isCurrentKeySet(keys) && this._isCurMouseKeySet(mouseKeys) && mouseEvents === "wheeldown",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._notify(e, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle click event and try found registered hotkey event
|
||||||
|
* @param zone - the zone from which the event came
|
||||||
|
* @returns found hotkeys event (if found)
|
||||||
|
*/
|
||||||
|
public handleClick(e: MouseEvent, zone: string = GLOBAL_ZONE_TOKEN): IKeyboardEvent<A> {
|
||||||
|
const event = this.getEventByKeys(zone, e, (keys, mouseEvent) => this._isCurrentKeySet(keys) && mouseEvent === "click");
|
||||||
|
this._notify(e, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle double click event and try found registered hotkey event
|
||||||
|
* @param zone - the zone from which the event came
|
||||||
|
* @returns found hotkeys event (if found)
|
||||||
|
*/
|
||||||
|
public handleDbClick(e: MouseEvent, zone: string = GLOBAL_ZONE_TOKEN): IKeyboardEvent<A> {
|
||||||
|
const event = this.getEventByKeys(
|
||||||
|
zone,
|
||||||
|
e,
|
||||||
|
(keys, mouseEvent, mouseKeys) => this._isCurrentKeySet(keys) && this._isCurMouseKeySet(mouseKeys) && mouseEvent === "dbclick",
|
||||||
|
);
|
||||||
|
this._notify(e, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse down event and try found registered hotkey event
|
||||||
|
* @param zone - the zone from which the event came
|
||||||
|
* @returns found hotkeys event (if found)
|
||||||
|
*/
|
||||||
|
public handleMouseDown(e: MouseEvent, zone: string = GLOBAL_ZONE_TOKEN): IKeyboardEvent<A> {
|
||||||
|
this._currentMouseDownKeys = new Set([]);
|
||||||
|
// * See: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
||||||
|
[
|
||||||
|
[1, MouseKey.LMB],
|
||||||
|
[2, MouseKey.RMB],
|
||||||
|
[4, MouseKey.MMB],
|
||||||
|
[8, MouseKey.BMB],
|
||||||
|
[16, MouseKey.FMB],
|
||||||
|
].forEach(([v, b]) => {
|
||||||
|
if (e.buttons >= v) {
|
||||||
|
this._currentMouseDownKeys.add(b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const event = this.getEventByKeys(
|
||||||
|
zone,
|
||||||
|
e,
|
||||||
|
(keys, mouseEvent, mouseKeys) => this._isCurrentKeySet(keys) && this._isCurMouseKeySet(mouseKeys) && !mouseEvent,
|
||||||
|
);
|
||||||
|
this._notify(e, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove key from list current mouse down keys */
|
||||||
|
public handleMouseUp(e: MouseEvent): void {
|
||||||
|
this._currentMouseDownKeys.delete(e.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleMove(e: MouseEvent, zone: string = GLOBAL_ZONE_TOKEN) {
|
||||||
|
const event = this.getEventByKeys(zone, e, (keys, mouseEvent) => this._isCurrentKeySet(keys) && mouseEvent === "mousemove");
|
||||||
|
this._notify(e, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isPressedKey(keyCode: number): boolean {
|
||||||
|
return this._currentDownKeys.includes(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _findHotkeyAction(zone: string, cb: FindKeyCb): [A | undefined, string] {
|
||||||
|
const foundAction = this._findAction(zone, cb);
|
||||||
|
return foundAction ? [foundAction, zone] : [this._findAction(GLOBAL_ZONE_TOKEN, cb), GLOBAL_ZONE_TOKEN];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isCurrentKeySet(keys: Key[][]): boolean {
|
||||||
|
return correspondsToKeySet(this._currentDownKeys, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isCurMouseKeySet(mouseKeys: MouseKey[]): boolean {
|
||||||
|
if (!mouseKeys && this._currentMouseDownKeys.size === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!mouseKeys || mouseKeys?.length !== this._currentMouseDownKeys.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mouseKeys.every((mk) => this._currentMouseDownKeys.has(mk));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _findAction(zone: string, cb: FindKeyCb): A | undefined {
|
||||||
|
let foundAction: A | undefined;
|
||||||
|
this._zonesMap.get(zone).forEach(({ mouseEvents, action, keys, mouseKeys }) => {
|
||||||
|
if (cb(keys, mouseEvents, mouseKeys)) {
|
||||||
|
foundAction = action;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return foundAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear current down keyboard keys */
|
||||||
|
public clearCurrentDownKeys(): void {
|
||||||
|
this._currentDownKeys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Allows you to check whether a hotkey or action is registered or not */
|
||||||
|
public isRegisteredHotkey(data: IKeyRegistry<A>): boolean {
|
||||||
|
const result = this._isRegisteredHotkey(new KeyRegistry<A>(data));
|
||||||
|
return result.isRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isRegisteredHotkey(data: KeyRegistry<A>): IRegistrationCheckResult {
|
||||||
|
const actionsMap = this._zonesMap.get(data.zone);
|
||||||
|
if (actionsMap == null) return { isRegistered: false };
|
||||||
|
if (actionsMap.get(data.action)) {
|
||||||
|
return { isRegistered: true, message: actionExistError(data.action) };
|
||||||
|
}
|
||||||
|
for (const registry of actionsMap.values()) {
|
||||||
|
const someHotkeyEqual = registry.keys.some((existKeySet) => data.keys.some((keySet) => arraysIsEqual(keySet, existKeySet)));
|
||||||
|
const mouseKeysEqual = arraysIsEqual(data.mouseKeys, registry.mouseKeys);
|
||||||
|
const mouseEventEqual = data.mouseEvents === registry.mouseEvents;
|
||||||
|
if (someHotkeyEqual && mouseKeysEqual && mouseEventEqual) {
|
||||||
|
return { isRegistered: true, message: hotkeyExistError(data, registry) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isRegistered: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this._isDestroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./hotkey-manager";
|
||||||
|
export * from "./utils";
|
||||||
|
export * from "./functions";
|
||||||
|
export * from "./interfaces";
|
||||||
|
export * from "./constants";
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
|
||||||
|
/** Describe the mouse events */
|
||||||
|
export type TMouseEvent = "click" | "dbclick" | "mousemove" | "wheelup" | "wheeldown";
|
||||||
|
// "mouseup" | "mousedown"
|
||||||
|
|
||||||
|
/** Interface for describe hotkey */
|
||||||
|
export interface IKeyRegistry<A = string> {
|
||||||
|
/** Type of mouse event */
|
||||||
|
mouseEvents?: TMouseEvent;
|
||||||
|
/** List of mouse keys. */
|
||||||
|
mouseKeys?: MouseKey[];
|
||||||
|
/** Action identifier */
|
||||||
|
action: A;
|
||||||
|
/** Label for hotkey */
|
||||||
|
label?: string;
|
||||||
|
/** List of hotkeys */
|
||||||
|
keys: Key[][];
|
||||||
|
/** Zone where hotkeys works */
|
||||||
|
zone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Describe keyboard event */
|
||||||
|
export interface IKeyboardEvent<A = string, P = any> {
|
||||||
|
/** Pressed keys */
|
||||||
|
keys: Key[];
|
||||||
|
/** Action identifier */
|
||||||
|
action: A;
|
||||||
|
/** Keyboard zone */
|
||||||
|
zone: string | "GLOBAL";
|
||||||
|
/** Event which provoked actuation of hotkey event */
|
||||||
|
triggeringEvent: Event;
|
||||||
|
/** Event payload. Can be any data */
|
||||||
|
payload?: P;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Describe types of mouse keys */
|
||||||
|
export enum MouseKey {
|
||||||
|
LMB = 0,
|
||||||
|
MMB = 1,
|
||||||
|
RMB = 2,
|
||||||
|
FMB = 3,
|
||||||
|
BMB = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Available types for actions */
|
||||||
|
export type Action = string | number;
|
||||||
|
|
||||||
|
/** Function type for check is the hotkey suitable or not */
|
||||||
|
export type FindKeyCb = (keys: Key[][], mouseEvents: TMouseEvent, mouseKeys: MouseKey[]) => boolean;
|
||||||
|
|
||||||
|
/** Description keyboard key label */
|
||||||
|
export interface IKeyLabel {
|
||||||
|
/** Key code */
|
||||||
|
code: number;
|
||||||
|
/** Label for key */
|
||||||
|
label: string;
|
||||||
|
/** Alternate label for key */
|
||||||
|
altLabel?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Describe OS types */
|
||||||
|
export enum OPERATING_SYSTEM {
|
||||||
|
WINDOWS,
|
||||||
|
MAC_OS,
|
||||||
|
IOS,
|
||||||
|
OTHER,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mac OS key codes */
|
||||||
|
export enum MAC_SPECIAL_KEYS {
|
||||||
|
/** 91 - key code for CmdLeft on macOS */
|
||||||
|
CmdLeft = 91,
|
||||||
|
/** 93 - key code for CmdRight on macOS */
|
||||||
|
CmdRight = 93,
|
||||||
|
/** 8 - key code for Backspace on macOS */
|
||||||
|
Backspace = 8,
|
||||||
|
/** 18 - key code for Option on macOS */
|
||||||
|
Option = 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Describes the result of registration checking */
|
||||||
|
export interface IRegistrationCheckResult {
|
||||||
|
/** `true` if action or hotkey is registered */
|
||||||
|
isRegistered: boolean;
|
||||||
|
/** Describes detail of checking */
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
28
lib/src/models/key-registry.ts
Normal file
28
lib/src/models/key-registry.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { GLOBAL_ZONE_TOKEN } from "../constants";
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
import { IKeyRegistry, MouseKey, TMouseEvent } from "../interfaces";
|
||||||
|
|
||||||
|
/** Describe data of hotkey */
|
||||||
|
export class KeyRegistry<T = string> implements IKeyRegistry<T> {
|
||||||
|
/** Type of mouse event */
|
||||||
|
mouseEvents: TMouseEvent;
|
||||||
|
/** List of mouse keys. */
|
||||||
|
mouseKeys: MouseKey[];
|
||||||
|
/** Action identifier */
|
||||||
|
action: T;
|
||||||
|
/** Label for hotkey */
|
||||||
|
label: string;
|
||||||
|
/** List of hotkeys */
|
||||||
|
keys: Key[][];
|
||||||
|
/** Zone where hotkeys works */
|
||||||
|
zone: string;
|
||||||
|
|
||||||
|
constructor(keyRegistry: IKeyRegistry<T>) {
|
||||||
|
this.mouseEvents = keyRegistry.mouseEvents;
|
||||||
|
this.mouseKeys = keyRegistry.mouseKeys ?? [];
|
||||||
|
this.action = keyRegistry.action;
|
||||||
|
this.keys = keyRegistry.keys;
|
||||||
|
this.label = keyRegistry.label;
|
||||||
|
this.zone = keyRegistry.zone ?? GLOBAL_ZONE_TOKEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "@gxc-solutions/lib",
|
"name": "@gxc-solutions/hotkeys",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "GXC Solutions",
|
"author": "GXC Solutions",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.gxc-solutions.ru"
|
"registry": "https://npm.gxc-solutions.ru"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@gxc-solutions/lett-js": "1.0.0",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
lib/src/utils/README.MD
Normal file
3
lib/src/utils/README.MD
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Описание
|
||||||
|
|
||||||
|
Эта директория предназначена для **непубличных** функций, т.е функций используемых внутри пакета.
|
||||||
31
lib/src/utils/index.spec.ts
Normal file
31
lib/src/utils/index.spec.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { getMacKeys } from ".";
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
import { MAC_SPECIAL_KEYS } from "../interfaces";
|
||||||
|
|
||||||
|
describe("getMacKeys", () => {
|
||||||
|
it("Correct 'CTRL' key transform", () => {
|
||||||
|
const result = getMacKeys([[Key.Ctrl, Key.A]]);
|
||||||
|
expect(result).toEqual([[MAC_SPECIAL_KEYS.CmdLeft as number, Key.A]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Correct 'DELETE' key transform", () => {
|
||||||
|
const result = getMacKeys([[Key.Delete, Key.A]]);
|
||||||
|
expect(result).toEqual([[MAC_SPECIAL_KEYS.Backspace as number, Key.A]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Correct 'ALT' key transform", () => {
|
||||||
|
const result = getMacKeys([[Key.Alt, Key.A]]);
|
||||||
|
expect(result).toEqual([[MAC_SPECIAL_KEYS.Option as number, Key.A]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Correct transform all key set", () => {
|
||||||
|
const result = getMacKeys([
|
||||||
|
[Key.Alt, Key.A],
|
||||||
|
[Key.Alt, Key.A],
|
||||||
|
]);
|
||||||
|
expect(result).toEqual([
|
||||||
|
[MAC_SPECIAL_KEYS.Option as number, Key.A],
|
||||||
|
[MAC_SPECIAL_KEYS.Option as number, Key.A],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
41
lib/src/utils/index.ts
Normal file
41
lib/src/utils/index.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Key } from "../constants/key";
|
||||||
|
import { IKeyRegistry, MAC_SPECIAL_KEYS } from "../interfaces";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt keyboard keys for another platform to Mac OS
|
||||||
|
* @param keys - array describing the hotkey. For example: [Key.Ctrl, Key.A]
|
||||||
|
* @returns keyboard keys adapted to the Mac OS
|
||||||
|
*/
|
||||||
|
export const getMacKeys = (keys: Key[][]): Key[][] =>
|
||||||
|
keys.map((keySet) =>
|
||||||
|
keySet.map((key) => {
|
||||||
|
switch (key) {
|
||||||
|
case Key.Delete:
|
||||||
|
return MAC_SPECIAL_KEYS.Backspace as number;
|
||||||
|
case Key.Ctrl:
|
||||||
|
return MAC_SPECIAL_KEYS.CmdLeft as number;
|
||||||
|
case Key.Alt:
|
||||||
|
return MAC_SPECIAL_KEYS.Option as number;
|
||||||
|
default:
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Does the key match the given keys set */
|
||||||
|
export const correspondsToKeySet = (keys: Key[], keySet: Key[][]) => keySet.some((set) => set.every((key, idx) => keys[idx] === key));
|
||||||
|
|
||||||
|
/** Add package name to message */
|
||||||
|
const addPackageName = (message: string) => `[@aurigma/ng-hotkeys] ${message}`;
|
||||||
|
|
||||||
|
/** Create message for error */
|
||||||
|
export const actionExistError = <A>(action: A) =>
|
||||||
|
addPackageName(`Attempt to register a hotkey with the action '${action}'. This action already exists. Call unregisterKeys() first.`);
|
||||||
|
|
||||||
|
/** Create message for error */
|
||||||
|
export const hotkeyExistError = <A>(data: IKeyRegistry<A>, exist: IKeyRegistry<A>) =>
|
||||||
|
addPackageName(
|
||||||
|
`Attempt to register a hotkey ${JSON.stringify(data)} in to zone '${data.zone}'. This hotkey already exists as ${JSON.stringify(
|
||||||
|
exist,
|
||||||
|
)}. Call unregisterKeys() first.`,
|
||||||
|
);
|
||||||
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -1,13 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "template-of-lib-repo",
|
"name": "gxc-hotkeys",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "template-of-lib-repo",
|
"name": "gxc-hotkeys",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@gxc-solutions/lett-js": "1.0.0",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.4",
|
"@eslint/eslintrc": "^3.3.4",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
|
@ -691,6 +695,11 @@
|
||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@gxc-solutions/lett-js": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://npm.gxc-solutions.ru/@gxc-solutions/lett-js/-/lett-js-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-9qkFVWS6ZRoMBGe4gPC9eR+6ozxIKFqm0nSbPqWywOzUJNpTQByuHHCtejTyIxrxLqyoASI8quiFtf9/UpWmow=="
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
|
|
@ -2331,6 +2340,15 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rxjs": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
|
@ -2420,6 +2438,12 @@
|
||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "template-of-lib-repo",
|
"name": "gxc-hotkeys",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -29,5 +29,9 @@
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@gxc-solutions/lett-js": "1.0.0",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
"outDir": "./dist"
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": ["./lib/src/**/*"],
|
"include": ["./lib/src/**/*"],
|
||||||
"exclude": ["src/**/*.test.ts"]
|
"exclude": ["./lib/src/**/*.test.ts", "./lib/src/**/*.spec.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue