@peter.naydenov/shortcuts 1.0.0
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 +11 -0
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/blueprint-docs.md +73 -0
- package/cypress.config.js +10 -0
- package/dist/assets/index-3c4377c6.js +1 -0
- package/dist/index.html +32 -0
- package/dist/vite.svg +1 -0
- package/index.html +73 -0
- package/javascript.svg +1 -0
- package/package.json +33 -0
- package/public/vite.svg +1 -0
- package/src/changeContext.js +51 -0
- package/src/findTarget.js +18 -0
- package/src/listen.js +186 -0
- package/src/load.js +32 -0
- package/src/main.js +92 -0
- package/src/readKeyEvent.js +27 -0
- package/src/readMouseEvent.js +24 -0
- package/src/readShortcut.js +16 -0
- package/src/specialChars.js +31 -0
- package/src/unload.js +23 -0
- package/style.css +97 -0
- package/test/01-general.cy.js +111 -0
- package/test-components/Block.jsx +11 -0
- package/test-components/style.css +21 -0
- package/vite.config.js +7 -0
package/src/listen.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function listen ( dependencies, options, currentContext ) { // Listen for input signals and generate event titles
|
|
6
|
+
const {
|
|
7
|
+
specialChars
|
|
8
|
+
, readKeyEvent
|
|
9
|
+
, readMouseEvent
|
|
10
|
+
, findTarget
|
|
11
|
+
, ev
|
|
12
|
+
, exposeShortcut
|
|
13
|
+
, streamKeys
|
|
14
|
+
} = dependencies
|
|
15
|
+
, {
|
|
16
|
+
mouseWait
|
|
17
|
+
, keyWait
|
|
18
|
+
, clickTarget
|
|
19
|
+
, listenFor
|
|
20
|
+
} = options
|
|
21
|
+
;
|
|
22
|
+
|
|
23
|
+
let
|
|
24
|
+
r = []
|
|
25
|
+
, mouseTarget = null // Dom element or null
|
|
26
|
+
, mouseDomEvent = null
|
|
27
|
+
, keyTimer = null // Timer for key sequence or null
|
|
28
|
+
, mouseTimer = null // Timer for mouse sequence or null
|
|
29
|
+
, mouseIgnore = null // Timer for ignoring mouse clicks or null
|
|
30
|
+
, sequence = true
|
|
31
|
+
, ignore = false // Use to trigger a single callback without adding the key to the sequence.
|
|
32
|
+
, count = 0
|
|
33
|
+
;
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const
|
|
39
|
+
waitKeys = () => sequence = false
|
|
40
|
+
, endKeys = () => sequence = true
|
|
41
|
+
, ignoreKeys = () => ignore = true
|
|
42
|
+
, waitingKeys = () => sequence === false
|
|
43
|
+
;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
function keySequenceEnd () { // Execute when key sequence ends
|
|
48
|
+
let res = r.map ( x => ([x.join('+')]) );
|
|
49
|
+
if ( !sequence ) {
|
|
50
|
+
let signal = res.at(-1);
|
|
51
|
+
ev.emit ( signal, { wait:waitKeys, end:endKeys, ignore:ignoreKeys, isWaiting:waitingKeys, note: currentContext.note, context: currentContext.name })
|
|
52
|
+
if ( ignore ) {
|
|
53
|
+
res = res.slice ( 0, -1 )
|
|
54
|
+
ignore = false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ( sequence ) {
|
|
59
|
+
ev.emit ( res.join(','), { wait: waitKeys, end:endKeys, ignore:ignoreKeys, isWaiting:waitingKeys, note: currentContext.note, context: currentContext.name })
|
|
60
|
+
if ( exposeShortcut ) exposeShortcut ( res.join(','), currentContext.name, currentContext.note ) // TODO: Add a context information...?
|
|
61
|
+
// Reset:
|
|
62
|
+
r = []
|
|
63
|
+
keyTimer = null
|
|
64
|
+
}
|
|
65
|
+
} // keySequeceEnd func.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function mouseSequenceEnd () { // Execute when mouse sequence ends
|
|
70
|
+
const
|
|
71
|
+
mouseEvent = readMouseEvent ( mouseDomEvent, count )
|
|
72
|
+
, data = {
|
|
73
|
+
target : mouseTarget
|
|
74
|
+
, targetProps : mouseTarget ? mouseTarget.getBoundingClientRect() : null
|
|
75
|
+
, x : mouseDomEvent.clientX
|
|
76
|
+
, y : mouseDomEvent.clientY
|
|
77
|
+
, context : currentContext.name
|
|
78
|
+
, note : currentContext.note
|
|
79
|
+
, event : mouseDomEvent
|
|
80
|
+
}
|
|
81
|
+
;
|
|
82
|
+
ev.emit ( mouseEvent.join('+'), data )
|
|
83
|
+
if ( exposeShortcut ) exposeShortcut ( mouseEvent.join('+'), currentContext.name, currentContext.note )
|
|
84
|
+
// Reset:
|
|
85
|
+
mouseTimer = null
|
|
86
|
+
mouseIgnore = null
|
|
87
|
+
mouseTarget = null
|
|
88
|
+
mouseDomEvent = null
|
|
89
|
+
count = 0
|
|
90
|
+
} // mouseSequenceEnd func.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function listenMouse () {
|
|
95
|
+
window.addEventListener ( 'contextmenu', event => { // Listen for right mouse clicks
|
|
96
|
+
clearTimeout ( mouseTimer )
|
|
97
|
+
if ( mouseIgnore ) {
|
|
98
|
+
clearTimeout ( mouseIgnore )
|
|
99
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if ( count === options.maxClicks ) {
|
|
103
|
+
mouseSequenceEnd ()
|
|
104
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
event.preventDefault ()
|
|
108
|
+
mouseTarget = findTarget (event.target, clickTarget )
|
|
109
|
+
mouseDomEvent = event
|
|
110
|
+
count++
|
|
111
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
document.addEventListener ( 'click', event => { // Listen for left and middle mouse clicks
|
|
115
|
+
clearTimeout ( mouseTimer )
|
|
116
|
+
if ( mouseIgnore ) {
|
|
117
|
+
clearTimeout ( mouseIgnore )
|
|
118
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if ( count === options.maxClicks ) {
|
|
122
|
+
mouseSequenceEnd ()
|
|
123
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
mouseTarget = findTarget ( event.target, clickTarget )
|
|
127
|
+
mouseDomEvent = event
|
|
128
|
+
count++
|
|
129
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
130
|
+
})
|
|
131
|
+
} // listenMouse func.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
function listenKeyboard () {
|
|
136
|
+
document.addEventListener ( 'keydown', event => { // Listen for special keyboard keys
|
|
137
|
+
clearTimeout ( keyTimer )
|
|
138
|
+
if ( specialChars.hasOwnProperty(event.code) ) r.push ( readKeyEvent ( event, specialChars ))
|
|
139
|
+
else return
|
|
140
|
+
if ( streamKeys ) streamKeys ( event.key, currentContext.name, currentContext.note )
|
|
141
|
+
if ( options.keyIgnore ) {
|
|
142
|
+
clearTimeout ( options.keyIgnore )
|
|
143
|
+
options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if ( sequence && r.length === options.maxSequence ) {
|
|
147
|
+
keySequenceEnd ()
|
|
148
|
+
options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
|
|
152
|
+
else keySequenceEnd ()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
document.addEventListener ( 'keypress', event => { // Listen for regular keyboard keys
|
|
156
|
+
if ( specialChars.hasOwnProperty(event.code) ) return
|
|
157
|
+
clearTimeout ( keyTimer )
|
|
158
|
+
if ( streamKeys ) streamKeys ( event.key, currentContext.name, currentContext.note )
|
|
159
|
+
if ( options.keyIgnore ) {
|
|
160
|
+
clearTimeout ( options.keyIgnore )
|
|
161
|
+
options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
r.push ( readKeyEvent ( event, specialChars ))
|
|
165
|
+
if ( sequence && r.length === options.maxSequence ) {
|
|
166
|
+
keySequenceEnd ()
|
|
167
|
+
options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
|
|
171
|
+
else keySequenceEnd ()
|
|
172
|
+
})
|
|
173
|
+
} // listenKeyboard func.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if ( listenFor.includes('mouse') ) listenMouse ()
|
|
178
|
+
if ( listenFor.includes('keyboard') ) listenKeyboard ()
|
|
179
|
+
|
|
180
|
+
} // listen func.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
export default listen
|
|
185
|
+
|
|
186
|
+
|
package/src/load.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function load ( shortcuts, readShortcut, changeContext, getContext ) {
|
|
6
|
+
return function load ( obj ) {
|
|
7
|
+
const
|
|
8
|
+
currentContextName = getContext ()
|
|
9
|
+
, contextList = Object.keys ( obj )
|
|
10
|
+
;
|
|
11
|
+
let hasChanges = false;
|
|
12
|
+
contextList.forEach ( contextName => {
|
|
13
|
+
const description = Object.entries ( obj [ contextName ] );
|
|
14
|
+
if ( contextName === currentContextName ) hasChanges = true;
|
|
15
|
+
shortcuts [ contextName ] = {}
|
|
16
|
+
description.forEach ( ([ title, callbackList ]) => {
|
|
17
|
+
const name = readShortcut ( title );
|
|
18
|
+
if ( callbackList instanceof Function ) callbackList = [ callbackList ]
|
|
19
|
+
shortcuts [contextName][ name ] = callbackList
|
|
20
|
+
}) // shortcuts.forEach
|
|
21
|
+
}) // contextList.forEach
|
|
22
|
+
if ( hasChanges ) { // Reload context shortcuts after loading process if context was active
|
|
23
|
+
changeContext ()
|
|
24
|
+
changeContext ( currentContextName )
|
|
25
|
+
}
|
|
26
|
+
}} // load func.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default load
|
|
31
|
+
|
|
32
|
+
|
package/src/main.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shortcuts
|
|
5
|
+
* ========
|
|
6
|
+
* Create shortcuts for your web application based on keyboard and mouse events.
|
|
7
|
+
* Repository: https://github.com/PeterNaydenov/shortcuts
|
|
8
|
+
*
|
|
9
|
+
* History notes:
|
|
10
|
+
* - Development was started on June 21st, 2023
|
|
11
|
+
* - First version was published on August 14th, 2023
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import notice from '@peter.naydenov/notice' // Docs: https://github.com/PeterNaydenov/notice
|
|
17
|
+
|
|
18
|
+
import listen from './listen.js'
|
|
19
|
+
import readShortcut from './readShortcut.js'
|
|
20
|
+
import readKeyEvent from './readKeyEvent.js'
|
|
21
|
+
import readMouseEvent from './readMouseEvent.js'
|
|
22
|
+
import findTarget from './findTarget.js'
|
|
23
|
+
import specialChars from './specialChars.js'
|
|
24
|
+
import load from './load.js'
|
|
25
|
+
import unload from './unload.js'
|
|
26
|
+
import changeContext from './changeContext.js'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
function main ( options = {} ) {
|
|
33
|
+
const
|
|
34
|
+
ev = notice () // Event emitter instance
|
|
35
|
+
, currentContext = { name: null, note: null } // Context data container
|
|
36
|
+
, exposeShortcut = (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false
|
|
37
|
+
, streamKeys = (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false
|
|
38
|
+
, listenOptions = {
|
|
39
|
+
mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
|
|
40
|
+
, maxClicks : 1 // The maximum number of clicks in a sequence. Controlled automatically by 'changeContext' function.
|
|
41
|
+
, keyWait : options.keyWait ? options.keyWait : 480 // 480 ms
|
|
42
|
+
, maxSequence : 1 // How many keys can be pressed in a sequence. Controlled automatically by 'changeContext' function.
|
|
43
|
+
, clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
|
|
44
|
+
, listenFor : (options.listenFor && Array.isArray(options.listenFor)) ? options.listenFor : [ 'mouse', 'keyboard' ] // What to listen for: ['mouse'], ['keyboard'], ['mouse', 'keyboard']
|
|
45
|
+
, keyIgnore : null // Timer for ignoring key presses after max sequence or null. Not a public option.
|
|
46
|
+
}
|
|
47
|
+
, shortcuts = {} // shortcuts = { contextName : { shortcut : callback[] } }
|
|
48
|
+
, getContext = () => currentContext.name
|
|
49
|
+
, getNote = () => currentContext.note
|
|
50
|
+
, setNote = (note=null) => { if (typeof note === 'string' || note == null ) currentContext.note = note }
|
|
51
|
+
, dependencies = {
|
|
52
|
+
specialChars
|
|
53
|
+
, readKeyEvent
|
|
54
|
+
, readMouseEvent
|
|
55
|
+
, findTarget
|
|
56
|
+
, ev
|
|
57
|
+
, exposeShortcut
|
|
58
|
+
, streamKeys
|
|
59
|
+
}
|
|
60
|
+
;
|
|
61
|
+
|
|
62
|
+
listen ( dependencies, listenOptions, currentContext )
|
|
63
|
+
|
|
64
|
+
return { // shortcuts API
|
|
65
|
+
load : load ( shortcuts, readShortcut, changeContext( shortcuts, listenOptions, ev, currentContext ), getContext )
|
|
66
|
+
, unload : unload ( shortcuts, ev, currentContext )
|
|
67
|
+
, changeContext : changeContext ( shortcuts, listenOptions, ev, currentContext )
|
|
68
|
+
, pause : () => ev.stop ()
|
|
69
|
+
, resume : () => ev.start ()
|
|
70
|
+
, listContexts : () => Object.keys ( shortcuts )
|
|
71
|
+
, getContext
|
|
72
|
+
, getNote
|
|
73
|
+
, setNote
|
|
74
|
+
}
|
|
75
|
+
} // main func.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
main.getDefaults = () => ({
|
|
80
|
+
mouseWait : 320 // 320 ms // TODO: Slow down. It's too fast at the moment.
|
|
81
|
+
, keyWait : 480 // 480 ms
|
|
82
|
+
, clickTarget : 'click' // Data-attribute name for click target ( data-click )
|
|
83
|
+
, listenFor : [ 'mouse', 'keyboard' ]
|
|
84
|
+
, onShortcut : false // Shortcut log function or false
|
|
85
|
+
, streamKeys : false // Stream keys function or false
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export default main
|
|
91
|
+
|
|
92
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function readKeyEvent ( event, specialChars ) {
|
|
5
|
+
let
|
|
6
|
+
{ shiftKey, altKey, ctrlKey } = event
|
|
7
|
+
, falseKeys = [ 'ControlLeft','ControlRight', 'ShiftLeft', 'ShiftRight', 'AltLeft', 'AltRight', 'Meta' ]
|
|
8
|
+
, key = event.code
|
|
9
|
+
.replace ( 'Key', '' )
|
|
10
|
+
.replace('Digit','')
|
|
11
|
+
, res = []
|
|
12
|
+
;
|
|
13
|
+
|
|
14
|
+
if ( ctrlKey ) res.push ( 'CTRL' )
|
|
15
|
+
if ( shiftKey ) res.push ( 'SHIFT' )
|
|
16
|
+
if ( altKey ) res.push ( 'ALT' )
|
|
17
|
+
|
|
18
|
+
if ( specialChars.hasOwnProperty ( key ) ) res.push ( specialChars[key].toUpperCase () )
|
|
19
|
+
else if ( !falseKeys.includes(key) ) res.push ( key.toUpperCase () )
|
|
20
|
+
return res.sort ()
|
|
21
|
+
} // readKeyEvent func.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export default readKeyEvent
|
|
26
|
+
|
|
27
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function readMouseEvent ( event, count ) {
|
|
5
|
+
let
|
|
6
|
+
{ shiftKey, altKey, ctrlKey, key, button } = event
|
|
7
|
+
, mouseNames = [ 'LEFT', 'MIDDLE', 'RIGHT' ]
|
|
8
|
+
, mouseEvent = `MOUSE-CLICK-${mouseNames[button]}-${count}`
|
|
9
|
+
, res = []
|
|
10
|
+
;
|
|
11
|
+
|
|
12
|
+
res.push ( mouseEvent )
|
|
13
|
+
if ( ctrlKey ) res.push ( 'CTRL' )
|
|
14
|
+
if ( shiftKey ) res.push ( 'SHIFT' )
|
|
15
|
+
if ( altKey ) res.push ( 'ALT' )
|
|
16
|
+
|
|
17
|
+
return res.sort ()
|
|
18
|
+
} // readMouseEvent func.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export default readMouseEvent
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// [ crtl+s, shift+alt+o]
|
|
3
|
+
function readShortcut ( txt ) {
|
|
4
|
+
const r = txt
|
|
5
|
+
.split ( ',' )
|
|
6
|
+
.map ( (x) => x.trim() )
|
|
7
|
+
.map ( (x) => x.split ( '+' ).map(x => x.toUpperCase()).sort().join('+') )
|
|
8
|
+
.join ( ',' );
|
|
9
|
+
return r
|
|
10
|
+
} // readShortcut func.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export default readShortcut
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'ArrowLeft' : 'LEFT'
|
|
3
|
+
, 'ArrowUp' : 'UP'
|
|
4
|
+
, 'ArrowRight' : 'RIGHT'
|
|
5
|
+
, 'ArrowDown' : 'DOWN'
|
|
6
|
+
, 'Enter' : 'ENTER'
|
|
7
|
+
, 'NumpadEnter' : 'ENTER'
|
|
8
|
+
, 'Escape' : 'ESC'
|
|
9
|
+
, 'Backspace' : 'BACKSPACE'
|
|
10
|
+
, 'Space' : 'SPACE'
|
|
11
|
+
, 'Tab' : 'TAB'
|
|
12
|
+
, 'Backquote' : '`'
|
|
13
|
+
, 'BracketLeft' : '['
|
|
14
|
+
, 'BracketRight': ']'
|
|
15
|
+
, 'Equal' : '='
|
|
16
|
+
, 'Slash' : '/'
|
|
17
|
+
, 'Backslash' : '\\'
|
|
18
|
+
, 'IntlBackslash' : '`'
|
|
19
|
+
, 'F1' : 'F1'
|
|
20
|
+
, 'F2' : 'F2'
|
|
21
|
+
, 'F3' : 'F3'
|
|
22
|
+
, 'F4' : 'F4'
|
|
23
|
+
, 'F5' : 'F5'
|
|
24
|
+
, 'F6' : 'F6'
|
|
25
|
+
, 'F7' : 'F7'
|
|
26
|
+
, 'F8' : 'F8'
|
|
27
|
+
, 'F9' : 'F9'
|
|
28
|
+
, 'F10' : 'F10'
|
|
29
|
+
, 'F11' : 'F11'
|
|
30
|
+
, 'F12' : 'F12'
|
|
31
|
+
}
|
package/src/unload.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function unload ( shortcuts, ev, currentContext ) {
|
|
6
|
+
return function unload ( contextName ) {
|
|
7
|
+
const current = currentContext.name;
|
|
8
|
+
if ( current === contextName ) {
|
|
9
|
+
ev.emit ( 'shortcuts-error', `Context '${ contextName }' can't be removed during is current active context. Change the context first` )
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
if ( !shortcuts [ contextName ] ) {
|
|
13
|
+
ev.emit ( 'shortcuts-error', `Context '${ contextName }' does not exist` )
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
delete shortcuts [ contextName ]
|
|
17
|
+
}} // unload func.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export default unload
|
|
22
|
+
|
|
23
|
+
|
package/style.css
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
-webkit-text-size-adjust: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
a {
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
color: #646cff;
|
|
20
|
+
text-decoration: inherit;
|
|
21
|
+
}
|
|
22
|
+
a:hover {
|
|
23
|
+
color: #535bf2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
margin: 0;
|
|
28
|
+
display: flex;
|
|
29
|
+
place-items: center;
|
|
30
|
+
min-width: 320px;
|
|
31
|
+
min-height: 100vh;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
h1 {
|
|
35
|
+
font-size: 3.2em;
|
|
36
|
+
line-height: 1.1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#app {
|
|
40
|
+
max-width: 1280px;
|
|
41
|
+
margin: 0 auto;
|
|
42
|
+
padding: 2rem;
|
|
43
|
+
text-align: center;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.logo {
|
|
47
|
+
height: 6em;
|
|
48
|
+
padding: 1.5em;
|
|
49
|
+
will-change: filter;
|
|
50
|
+
transition: filter 300ms;
|
|
51
|
+
}
|
|
52
|
+
.logo:hover {
|
|
53
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
54
|
+
}
|
|
55
|
+
.logo.vanilla:hover {
|
|
56
|
+
filter: drop-shadow(0 0 2em #f7df1eaa);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.card {
|
|
60
|
+
padding: 2em;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.read-the-docs {
|
|
64
|
+
color: #888;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
button {
|
|
68
|
+
border-radius: 8px;
|
|
69
|
+
border: 1px solid transparent;
|
|
70
|
+
padding: 0.6em 1.2em;
|
|
71
|
+
font-size: 1em;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
font-family: inherit;
|
|
74
|
+
background-color: #1a1a1a;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
transition: border-color 0.25s;
|
|
77
|
+
}
|
|
78
|
+
button:hover {
|
|
79
|
+
border-color: #646cff;
|
|
80
|
+
}
|
|
81
|
+
button:focus,
|
|
82
|
+
button:focus-visible {
|
|
83
|
+
outline: 4px auto -webkit-focus-ring-color;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-color-scheme: light) {
|
|
87
|
+
:root {
|
|
88
|
+
color: #213547;
|
|
89
|
+
background-color: #ffffff;
|
|
90
|
+
}
|
|
91
|
+
a:hover {
|
|
92
|
+
color: #747bff;
|
|
93
|
+
}
|
|
94
|
+
button {
|
|
95
|
+
background-color: #f9f9f9;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
import Block from '../test-components/Block.jsx'
|
|
3
|
+
import '../test-components/style.css'
|
|
4
|
+
import shortcuts from '../src/main.js'
|
|
5
|
+
import { expect } from 'chai'
|
|
6
|
+
|
|
7
|
+
let
|
|
8
|
+
a = false
|
|
9
|
+
, b = false
|
|
10
|
+
;
|
|
11
|
+
const short = shortcuts ({onShortcut : ( shortcut, context, note ) => console.log (shortcut, context, note)});
|
|
12
|
+
short.load ({
|
|
13
|
+
general : {
|
|
14
|
+
'shift+a': [ () => a = true ]
|
|
15
|
+
}
|
|
16
|
+
, extra : {
|
|
17
|
+
'shift+a,p,r,o,b,a,ctrl+m' : () => b = true
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe ( 'Shortcuts', () => {
|
|
23
|
+
|
|
24
|
+
beforeEach ( () => {
|
|
25
|
+
cy.mount ( Block () )
|
|
26
|
+
a = false, b = false
|
|
27
|
+
}) // beforeEach
|
|
28
|
+
|
|
29
|
+
it ( 'Simple shortcut', done => {
|
|
30
|
+
let res = false;
|
|
31
|
+
short.changeContext ( 'general' )
|
|
32
|
+
cy.get('body').type ( '{shift}a' )
|
|
33
|
+
cy.wait ( 100 ) // Default wait sequence timeout is 480 ms
|
|
34
|
+
.then ( () => {
|
|
35
|
+
expect ( a ).to.be.true
|
|
36
|
+
done ()
|
|
37
|
+
})
|
|
38
|
+
}) // it first test
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
it ( 'Check context switching and shortcut sequences', done => {
|
|
43
|
+
expect ( a ).to.be.false
|
|
44
|
+
expect ( b ).to.be.false
|
|
45
|
+
|
|
46
|
+
short.changeContext ( 'extra' )
|
|
47
|
+
cy.get('body').type ( '{shift}a' )
|
|
48
|
+
cy.wait ( 500 ) // Default wait sequence timeout is 480 ms. Context 'extra' has a sequence of 7 keys. Need to wait for timeout
|
|
49
|
+
.then ( () => {
|
|
50
|
+
expect ( a ).to.be.false
|
|
51
|
+
|
|
52
|
+
cy.get('body')
|
|
53
|
+
.type ( '{shift}a' )
|
|
54
|
+
.type('proba')
|
|
55
|
+
.type('{ctrl}M')
|
|
56
|
+
return cy.wait ( 1 ) // Shortuct sequence is 7 keys - the maximum number of keys for this context. Don't need to wait for timeout
|
|
57
|
+
})
|
|
58
|
+
.then ( () => {
|
|
59
|
+
expect ( b ).to.be.true
|
|
60
|
+
|
|
61
|
+
short.changeContext ( 'general' )
|
|
62
|
+
cy.get('body').type ( '{shift}a' )
|
|
63
|
+
return cy.wait ( 2 ) // Context 'general' has a sequence of 1 key. Don't need to wait for timeout
|
|
64
|
+
})
|
|
65
|
+
.then ( () => {
|
|
66
|
+
expect ( a ).to.be.true
|
|
67
|
+
done ()
|
|
68
|
+
})
|
|
69
|
+
}) // it context and shortcut sequences
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
it ( 'Single mouse click', done => {
|
|
74
|
+
expect ( a ).to.be.false
|
|
75
|
+
expect ( b ).to.be.false
|
|
76
|
+
short.load ({ 'extra' : {
|
|
77
|
+
'mouse-click-left-1' : () => a = true
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
short.changeContext ( 'extra' )
|
|
81
|
+
cy.get('#rspan').click ()
|
|
82
|
+
cy.wait ( 350 ) // Default wait mouse timeout is 320 ms
|
|
83
|
+
.then ( () => {
|
|
84
|
+
expect ( a ).to.be.true
|
|
85
|
+
done ()
|
|
86
|
+
})
|
|
87
|
+
}) // it mouse click
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
it ( 'Double mouse click', done => {
|
|
92
|
+
expect ( a ).to.be.false
|
|
93
|
+
expect ( b ).to.be.false
|
|
94
|
+
|
|
95
|
+
short.changeContext ( 'extra' )
|
|
96
|
+
|
|
97
|
+
short.load ({
|
|
98
|
+
'extra' : { // load will overwrite existing 'extra' context definition
|
|
99
|
+
'mouse-click-left-2' : () => a = true
|
|
100
|
+
}
|
|
101
|
+
}) // load will restart the selected context
|
|
102
|
+
|
|
103
|
+
cy.get('#rspan').click().click ().click () // Third click is ignored. Max clicks according definition is 2.
|
|
104
|
+
cy.wait ( 350 ) // Default wait mouse timeout is 320 ms
|
|
105
|
+
.then ( () => {
|
|
106
|
+
expect ( a ).to.be.true
|
|
107
|
+
done ()
|
|
108
|
+
})
|
|
109
|
+
}) // it double mouse click
|
|
110
|
+
|
|
111
|
+
}) // describe
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.block {
|
|
2
|
+
width: 100px;
|
|
3
|
+
height: 100px;
|
|
4
|
+
padding: 20px;
|
|
5
|
+
background-color: red;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.block span {
|
|
9
|
+
color: white;
|
|
10
|
+
font-size: 20px;
|
|
11
|
+
font-weight: bold;
|
|
12
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.big-btn {
|
|
16
|
+
width: 200px;
|
|
17
|
+
height: 200px;
|
|
18
|
+
margin-left: 230px;
|
|
19
|
+
background-color: skyblue;
|
|
20
|
+
border-radius: 10px;
|
|
21
|
+
}
|