@peter.naydenov/shortcuts 2.2.0 → 3.0.1
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/Changelog.md +16 -2
- package/How..to.make.plugins.md +41 -0
- package/Migration.guide.md +77 -0
- package/README-v.2.x.x.md +375 -0
- package/README.md +106 -58
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/package.json +5 -5
- package/src/main.js +81 -30
- package/src/methods/_normalizeWithPlugins.js +25 -0
- package/src/methods/_readShortcutWithPlugins.js +24 -0
- package/src/methods/_systemAction.js +25 -0
- package/src/methods/changeContext.js +20 -26
- package/src/methods/index.js +7 -13
- package/src/methods/load.js +21 -14
- package/src/plugins/click/_findTarget.js +20 -0
- package/src/plugins/click/_listenDOM.js +117 -0
- package/src/plugins/click/_normalizeShortcutName.js +44 -0
- package/src/plugins/click/_readClickEvent.js +24 -0
- package/src/plugins/click/_registerShortcutEvents.js +30 -0
- package/src/plugins/click/index.js +74 -0
- package/src/plugins/key/_listenDOM.js +138 -0
- package/src/plugins/key/_normalizeShortcutName.js +31 -0
- package/src/{methods → plugins/key}/_readKeyEvent.js +2 -3
- package/src/plugins/key/_registerShortcutEvents.js +28 -0
- package/src/plugins/key/index.js +76 -0
- package/test/01-general.cy.js +189 -154
- package/src/methods/_findTarget.js +0 -19
- package/src/methods/_listen.js +0 -210
- package/src/methods/_readMouseEvent.js +0 -24
- package/src/methods/_readShortcut.js +0 -17
- /package/src/{methods → plugins/key}/_specialChars.js +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import _findTarget from "./_findTarget"
|
|
4
|
+
import _listenDOM from "./_listenDOM"
|
|
5
|
+
import _normalizeShortcutName from "./_normalizeShortcutName"
|
|
6
|
+
import _readClickEvent from "./_readClickEvent"
|
|
7
|
+
import _registerShortcutEvents from "./_registerShortcutEvents"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function pluginClick ( dependencies, state, options ) {
|
|
13
|
+
let
|
|
14
|
+
{ currentContext, shortcuts } = state
|
|
15
|
+
, { inAPI } = dependencies
|
|
16
|
+
, deps = {
|
|
17
|
+
ev: dependencies.ev
|
|
18
|
+
, _findTarget
|
|
19
|
+
, _readClickEvent
|
|
20
|
+
, mainDependencies : dependencies
|
|
21
|
+
, regex : /CLICK\s*\:/i
|
|
22
|
+
}
|
|
23
|
+
, pluginState = {
|
|
24
|
+
currentContext
|
|
25
|
+
, shortcuts
|
|
26
|
+
, listenOptions : {
|
|
27
|
+
mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
|
|
28
|
+
, maxClicks : 1 // How many clicks can be pressed in a sequence. Controlled automatically by '_registerShortcutEvents' function.
|
|
29
|
+
, clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
|
|
30
|
+
}
|
|
31
|
+
} // pluginState
|
|
32
|
+
;
|
|
33
|
+
|
|
34
|
+
// Read shortcuts names from all context entities and normalize entries related to the plugin
|
|
35
|
+
inAPI._normalizeWithPlugins ( _normalizeShortcutName )
|
|
36
|
+
|
|
37
|
+
let
|
|
38
|
+
mouseListener = _listenDOM ( deps, pluginState )
|
|
39
|
+
, countShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
40
|
+
;
|
|
41
|
+
|
|
42
|
+
if ( countShortcuts > 0 ) mouseListener.start ()
|
|
43
|
+
|
|
44
|
+
let pluginAPI = {
|
|
45
|
+
getPrefix : () => 'click'
|
|
46
|
+
, shortcutName : key => { // Format a key string according plugin needs
|
|
47
|
+
return _normalizeShortcutName ( key )
|
|
48
|
+
}
|
|
49
|
+
, contextChange : () => {
|
|
50
|
+
countShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
51
|
+
if ( countShortcuts < 1 ) { // Remove DOM listener if there are no shortcuts in the current context
|
|
52
|
+
mouseListener.stop ()
|
|
53
|
+
}
|
|
54
|
+
if ( countShortcuts > 0 ) { // Add DOM listener if there are shortcuts in the current context
|
|
55
|
+
mouseListener.start ()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
, mute : () => mouseListener.stop ()
|
|
59
|
+
, unmute : () => mouseListener.start ()
|
|
60
|
+
, destroy : () => {
|
|
61
|
+
mouseListener.stop ()
|
|
62
|
+
pluginState = null
|
|
63
|
+
pluginAPI = null
|
|
64
|
+
}
|
|
65
|
+
}; // pluginAPI
|
|
66
|
+
Object.freeze ( pluginAPI )
|
|
67
|
+
return pluginAPI
|
|
68
|
+
} // pluginClick func.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export default pluginClick
|
|
73
|
+
|
|
74
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function _listenDOM ( dependencies, state ) {
|
|
6
|
+
// Listen for input signals and generate event titles
|
|
7
|
+
const {
|
|
8
|
+
ev
|
|
9
|
+
, _specialChars
|
|
10
|
+
, _readKeyEvent
|
|
11
|
+
, mainDependencies
|
|
12
|
+
} = dependencies
|
|
13
|
+
, {
|
|
14
|
+
currentContext
|
|
15
|
+
, streamKeys
|
|
16
|
+
, listenOptions
|
|
17
|
+
} = state
|
|
18
|
+
, {
|
|
19
|
+
keyWait
|
|
20
|
+
} = listenOptions
|
|
21
|
+
;
|
|
22
|
+
|
|
23
|
+
let
|
|
24
|
+
r = []
|
|
25
|
+
, keyTimer = null // Timer for key sequence or null
|
|
26
|
+
, sequence = true
|
|
27
|
+
, ignore = false // Use to trigger a single callback without adding the key to the sequence.
|
|
28
|
+
;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const
|
|
33
|
+
waitKeys = () => sequence = false
|
|
34
|
+
, endKeys = () => sequence = true
|
|
35
|
+
, ignoreKeys = () => ignore = true
|
|
36
|
+
, waitingKeys = () => sequence === false
|
|
37
|
+
;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function keySequenceEnd () { // Execute when key sequence ends
|
|
42
|
+
let res = r.map ( x => ([x.join('+')]) )
|
|
43
|
+
const data = {
|
|
44
|
+
wait: waitKeys
|
|
45
|
+
, end:endKeys
|
|
46
|
+
, ignore:ignoreKeys
|
|
47
|
+
, isWaiting:waitingKeys
|
|
48
|
+
, note: currentContext.note
|
|
49
|
+
, context: currentContext.name
|
|
50
|
+
, dependencies : mainDependencies.extra
|
|
51
|
+
, type : 'key'
|
|
52
|
+
};
|
|
53
|
+
if ( !sequence ) {
|
|
54
|
+
let signal = res.at(-1);
|
|
55
|
+
ev.emit ( signal, data )
|
|
56
|
+
if ( ignore ) {
|
|
57
|
+
res = res.slice ( 0, -1 )
|
|
58
|
+
ignore = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ( sequence ) {
|
|
63
|
+
const signal = `KEY:${res.join(',')}`
|
|
64
|
+
ev.emit ( signal, data )
|
|
65
|
+
// Reset:
|
|
66
|
+
r = []
|
|
67
|
+
keyTimer = null
|
|
68
|
+
}
|
|
69
|
+
} // keySequeceEnd func.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
function listenForSpecialKeys ( event ) { // Listen for special keyboard keys
|
|
74
|
+
clearTimeout ( keyTimer )
|
|
75
|
+
if ( _specialChars.hasOwnProperty(event.code) ) r.push ( _readKeyEvent ( event, _specialChars ))
|
|
76
|
+
else return
|
|
77
|
+
if ( streamKeys ) streamKeys ({ key:event.key, context:currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
|
|
78
|
+
if ( listenOptions.keyIgnore ) {
|
|
79
|
+
clearTimeout ( listenOptions.keyIgnore )
|
|
80
|
+
listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
if ( sequence && r.length === listenOptions.maxSequence ) {
|
|
84
|
+
keySequenceEnd ()
|
|
85
|
+
listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
|
|
89
|
+
else keySequenceEnd ()
|
|
90
|
+
} // listenForSpecialKeys func.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function listenForRegularKeys ( event ) { // Listen for regular keyboard keys
|
|
95
|
+
if ( _specialChars.hasOwnProperty(event.code) ) return
|
|
96
|
+
clearTimeout ( keyTimer )
|
|
97
|
+
if ( streamKeys ) streamKeys ({ key:event.key, context:currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
|
|
98
|
+
if ( listenOptions.keyIgnore ) {
|
|
99
|
+
clearTimeout ( listenOptions.keyIgnore )
|
|
100
|
+
listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
r.push ( _readKeyEvent ( event, _specialChars ))
|
|
104
|
+
if ( sequence && r.length === listenOptions.maxSequence ) {
|
|
105
|
+
keySequenceEnd ()
|
|
106
|
+
listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
|
|
110
|
+
else keySequenceEnd ()
|
|
111
|
+
} // listenForRegularKeys func.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
function start () {
|
|
116
|
+
if ( state.active ) return
|
|
117
|
+
document.addEventListener ( 'keydown' , listenForSpecialKeys )
|
|
118
|
+
document.addEventListener ( 'keypress', listenForRegularKeys )
|
|
119
|
+
state.active = true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
function stop () {
|
|
124
|
+
if ( !state.active ) return
|
|
125
|
+
document.removeEventListener ( 'keydown' , listenForSpecialKeys )
|
|
126
|
+
document.removeEventListener ( 'keypress', listenForRegularKeys )
|
|
127
|
+
state.active = false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { start, stop }
|
|
131
|
+
|
|
132
|
+
} // _listenDOM func.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
export default _listenDOM
|
|
137
|
+
|
|
138
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _normalizeShortcutName ( name ) {
|
|
4
|
+
const
|
|
5
|
+
upperCase = name.toUpperCase ()
|
|
6
|
+
, regex = /KEY\s*\:/i
|
|
7
|
+
, isKeyboardShortcut = regex.test ( upperCase )
|
|
8
|
+
, sliceIndex = upperCase.indexOf ( ':' )
|
|
9
|
+
;
|
|
10
|
+
|
|
11
|
+
if ( !isKeyboardShortcut ) return name
|
|
12
|
+
let shortcut = upperCase
|
|
13
|
+
.slice(sliceIndex+1)
|
|
14
|
+
.split(',')
|
|
15
|
+
.map ( key => key.trim() )
|
|
16
|
+
.map ( key => {
|
|
17
|
+
return key
|
|
18
|
+
.split ( '+' )
|
|
19
|
+
.map ( key => key.trim() )
|
|
20
|
+
.sort()
|
|
21
|
+
.join ( '+' )
|
|
22
|
+
})
|
|
23
|
+
.join(',');
|
|
24
|
+
return `KEY:${shortcut}`
|
|
25
|
+
} // _normalizeShortcutName func.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export default _normalizeShortcutName
|
|
30
|
+
|
|
31
|
+
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
function _readKeyEvent () {
|
|
4
|
-
return function _readKeyEvent ( event, _specialChars ) {
|
|
3
|
+
function _readKeyEvent ( event, _specialChars ) {
|
|
5
4
|
let
|
|
6
5
|
{ shiftKey, altKey, ctrlKey } = event
|
|
7
6
|
, falseKeys = [ 'ControlLeft','ControlRight', 'ShiftLeft', 'ShiftRight', 'AltLeft', 'AltRight', 'Meta' ]
|
|
@@ -18,7 +17,7 @@ return function _readKeyEvent ( event, _specialChars ) {
|
|
|
18
17
|
if ( _specialChars.hasOwnProperty ( key ) ) res.push ( _specialChars[key].toUpperCase () )
|
|
19
18
|
else if ( !falseKeys.includes(key) ) res.push ( key.toUpperCase () )
|
|
20
19
|
return res.sort ()
|
|
21
|
-
}
|
|
20
|
+
} // _readKeyEvent func.
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _registerShortcutEvents ( dependencies, pluginState ) {
|
|
4
|
+
let count = 0;
|
|
5
|
+
const
|
|
6
|
+
{ regex } = dependencies
|
|
7
|
+
, {
|
|
8
|
+
listenOptions
|
|
9
|
+
, currentContext : { name: contextName }
|
|
10
|
+
, shortcuts
|
|
11
|
+
} = pluginState
|
|
12
|
+
;
|
|
13
|
+
if ( contextName == null ) return 0
|
|
14
|
+
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => { // Enable new context shortcuts and set a listenOptions 'maxSequence'
|
|
15
|
+
let isKeyboardEv = regex.test ( shortcutName );
|
|
16
|
+
if ( !isKeyboardEv ) return
|
|
17
|
+
count++
|
|
18
|
+
let sequenceArraySize = shortcutName.slice(4).split(',').length;
|
|
19
|
+
if ( listenOptions.maxSequence < sequenceArraySize ) listenOptions.maxSequence = sequenceArraySize
|
|
20
|
+
})
|
|
21
|
+
return count
|
|
22
|
+
} // _registerShortcutEvents func.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export default _registerShortcutEvents
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// import all plugin files here
|
|
4
|
+
import _listenDOM from './_listenDOM.js'
|
|
5
|
+
import _normalizeShortcutName from './_normalizeShortcutName.js'
|
|
6
|
+
import _readKeyEvent from './_readKeyEvent.js'
|
|
7
|
+
import _registerShortcutEvents from './_registerShortcutEvents.js'
|
|
8
|
+
import _specialChars from './_specialChars.js'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function pluginKey ( dependencies, state, options={} ) {
|
|
13
|
+
let
|
|
14
|
+
{ currentContext, shortcuts, exposeShortcut } = state
|
|
15
|
+
, { inAPI } = dependencies
|
|
16
|
+
, deps = {
|
|
17
|
+
ev: dependencies.ev
|
|
18
|
+
, _specialChars
|
|
19
|
+
, _readKeyEvent
|
|
20
|
+
, mainDependencies : dependencies
|
|
21
|
+
, regex : /KEY\s*\:/i
|
|
22
|
+
}
|
|
23
|
+
, pluginState = {
|
|
24
|
+
currentContext
|
|
25
|
+
, shortcuts
|
|
26
|
+
, active : false
|
|
27
|
+
, listenOptions : {
|
|
28
|
+
keyWait : options.keyWait ? options.keyWait : 480 // 480 ms
|
|
29
|
+
, maxSequence : 1 // How many keys can be pressed in a sequence. Controlled automatically by 'changeContext' function.
|
|
30
|
+
, keyIgnore : null // Timer for ignoring key presses after max sequence or null. Not a public option.
|
|
31
|
+
}
|
|
32
|
+
, streamKeys : (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false // Keyboard stream function
|
|
33
|
+
, exposeShortcut
|
|
34
|
+
}; // state
|
|
35
|
+
|
|
36
|
+
// Read shortcuts names from all context entities and normalize entries related to the plugin
|
|
37
|
+
inAPI._normalizeWithPlugins ( _normalizeShortcutName )
|
|
38
|
+
|
|
39
|
+
let
|
|
40
|
+
keysListener = _listenDOM ( deps, pluginState )
|
|
41
|
+
, countShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
42
|
+
;
|
|
43
|
+
|
|
44
|
+
if ( countShortcuts > 0 ) keysListener.start ()
|
|
45
|
+
|
|
46
|
+
let pluginAPI = {
|
|
47
|
+
getPrefix : () => 'key'
|
|
48
|
+
, shortcutName : key => { // Format a key string according plugin needs
|
|
49
|
+
return _normalizeShortcutName ( key )
|
|
50
|
+
}
|
|
51
|
+
, contextChange : contextName => {
|
|
52
|
+
countShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
53
|
+
if ( countShortcuts < 1 ) { // Remove DOM listener if there are no shortcuts in the current context
|
|
54
|
+
keysListener.stop ()
|
|
55
|
+
}
|
|
56
|
+
if ( countShortcuts > 0 ) { // Add DOM listener if there are shortcuts in the current context
|
|
57
|
+
keysListener.start ()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
, mute : () => keysListener.stop ()
|
|
61
|
+
, unmute : () => keysListener.start ()
|
|
62
|
+
, destroy : () => {
|
|
63
|
+
keysListener.stop ()
|
|
64
|
+
pluginState = null
|
|
65
|
+
pluginAPI = null
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
Object.freeze ( pluginAPI )
|
|
69
|
+
return pluginAPI
|
|
70
|
+
} // pluginKey func.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export default pluginKey
|
|
75
|
+
|
|
76
|
+
|