@peter.naydenov/shortcuts 2.2.0 → 3.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 +13 -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
package/src/main.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Shortcuts
|
|
5
5
|
* ========
|
|
6
|
+
*
|
|
6
7
|
* Create shortcuts for your web application based on keyboard and mouse events.
|
|
7
8
|
* Repository: https://github.com/PeterNaydenov/shortcuts
|
|
8
9
|
*
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
* - First version was published on August 14th, 2023
|
|
12
13
|
* - Method 'emit' was added on September 30st, 2023
|
|
13
14
|
* - Version 2.0.0 was published on October 16th, 2023
|
|
15
|
+
* - Version 3.0.0. Plugin system. Published on March 5th, 2024
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
|
|
@@ -18,11 +20,16 @@
|
|
|
18
20
|
import notice from '@peter.naydenov/notice' // Docs: https://github.com/PeterNaydenov/notice
|
|
19
21
|
import methods from './methods/index.js'
|
|
20
22
|
|
|
23
|
+
// Plugins
|
|
24
|
+
import pluginKey from './plugins/key/index.js'
|
|
25
|
+
import pluginClick from './plugins/click/index.js'
|
|
26
|
+
|
|
21
27
|
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
function main ( options = {} ) {
|
|
32
|
+
|
|
26
33
|
const
|
|
27
34
|
ev = notice () // Event emitter instance
|
|
28
35
|
, inAPI = {} // API for internal methods
|
|
@@ -30,17 +37,8 @@ function main ( options = {} ) {
|
|
|
30
37
|
, state = {
|
|
31
38
|
currentContext : { name: null, note: null } // Context data container
|
|
32
39
|
, shortcuts : {} // shortcuts = { contextName : { shortcut : callback[] } }
|
|
33
|
-
,
|
|
34
|
-
|
|
35
|
-
, maxClicks : 1 // The maximum number of clicks in a sequence. Controlled automatically by 'changeContext' function.
|
|
36
|
-
, keyWait : options.keyWait ? options.keyWait : 480 // 480 ms
|
|
37
|
-
, maxSequence : 1 // How many keys can be pressed in a sequence. Controlled automatically by 'changeContext' function.
|
|
38
|
-
, clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
|
|
39
|
-
, listenFor : (options.listenFor && Array.isArray(options.listenFor)) ? options.listenFor : [ 'mouse', 'keyboard' ] // What to listen for: ['mouse'], ['keyboard'], ['mouse', 'keyboard']
|
|
40
|
-
, keyIgnore : null // Timer for ignoring key presses after max sequence or null. Not a public option.
|
|
41
|
-
}
|
|
42
|
-
, exposeShortcut : (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false
|
|
43
|
-
, streamKeys : (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false
|
|
40
|
+
, plugins : [] // Array of active plugins
|
|
41
|
+
, exposeShortcut : (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false // Keyboard shortcut log function
|
|
44
42
|
} // state
|
|
45
43
|
, dependencies = {
|
|
46
44
|
ev
|
|
@@ -50,6 +48,58 @@ function main ( options = {} ) {
|
|
|
50
48
|
}
|
|
51
49
|
;
|
|
52
50
|
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// ---------------------- > PLUGIN METHODS < ---------------------- //
|
|
54
|
+
/**
|
|
55
|
+
* @function enablePlugin
|
|
56
|
+
* @description Enable a plugin
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
API.enablePlugin = ( plugin,options={}) => {
|
|
60
|
+
const
|
|
61
|
+
name = plugin.name
|
|
62
|
+
, ix = inAPI._systemAction ( name, 'none' )
|
|
63
|
+
;
|
|
64
|
+
|
|
65
|
+
if ( ix === -1 ) { // If plugin is not registered
|
|
66
|
+
let plugApp; // Started instance of the plugin
|
|
67
|
+
plugApp = plugin ( dependencies, state, options )
|
|
68
|
+
state.plugins.push ( plugApp )
|
|
69
|
+
}
|
|
70
|
+
} // enable func.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @function disablePlugin
|
|
76
|
+
* @description Disable a plugin
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
API.disablePlugin = pluginName => {
|
|
80
|
+
const ix = inAPI._systemAction ( pluginName, 'destroy' );
|
|
81
|
+
if ( ix !== -1 ) state.plugins = state.plugins.filter ( (plugin, i) => i !== ix )
|
|
82
|
+
} // disable func.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @function mutePlugin
|
|
87
|
+
* @description Mute a plugin
|
|
88
|
+
* @returns number - Index of the plugin in the plugins array ( -1 if not found ).
|
|
89
|
+
*/
|
|
90
|
+
API.mutePlugin = pluginName => inAPI._systemAction ( pluginName, 'mute' )
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @function unmute
|
|
94
|
+
* @description Unmute a plugin
|
|
95
|
+
* @returns number - Index of the plugin in the plugins array ( -1 if not found ).
|
|
96
|
+
*/
|
|
97
|
+
API.unmutePlugin = pluginName => inAPI._systemAction ( pluginName, 'unmute' )
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
// ---------------------- > PUBLIC METHODS < ---------------------- //
|
|
53
103
|
/**
|
|
54
104
|
* @function getContext
|
|
55
105
|
* @description Get current context name
|
|
@@ -78,7 +128,10 @@ function main ( options = {} ) {
|
|
|
78
128
|
* @param {string} [name='*' ] - Shortcut name that should be paused. Default is '*' - all shortcuts in current context.
|
|
79
129
|
* @returns {void}
|
|
80
130
|
*/
|
|
81
|
-
API.pause = (name='*') =>
|
|
131
|
+
API.pause = (name='*') => {
|
|
132
|
+
let pausedEvent = inAPI._readShortcutWithPlugins ( name );
|
|
133
|
+
ev.stop ( pausedEvent )
|
|
134
|
+
}
|
|
82
135
|
|
|
83
136
|
/**
|
|
84
137
|
* @function resume
|
|
@@ -86,7 +139,10 @@ function main ( options = {} ) {
|
|
|
86
139
|
* @param {string} [name='*' ] - Shortcut name that should be resumed. Default is '*' - all shortcuts in current context.
|
|
87
140
|
* @returns {void}
|
|
88
141
|
*/
|
|
89
|
-
API.resume = (name='*') =>
|
|
142
|
+
API.resume = (name='*') => {
|
|
143
|
+
const resumedEvent = inAPI._readShortcutWithPlugins ( name )
|
|
144
|
+
ev.start ( resumedEvent )
|
|
145
|
+
}
|
|
90
146
|
|
|
91
147
|
/**
|
|
92
148
|
* @function emit
|
|
@@ -95,14 +151,17 @@ function main ( options = {} ) {
|
|
|
95
151
|
* @param {any} [args] - Arguments for callback function
|
|
96
152
|
* @returns {void}
|
|
97
153
|
**/
|
|
98
|
-
API.emit = (name,...args) =>
|
|
154
|
+
API.emit = (name,...args) => ev.emit ( inAPI._readShortcutWithPlugins ( name ), ...args )
|
|
155
|
+
|
|
99
156
|
|
|
100
157
|
/**
|
|
101
158
|
* @function listContexts
|
|
102
159
|
* @description List all context names
|
|
103
160
|
* @returns {string[]} - Array of context names
|
|
104
161
|
*/
|
|
105
|
-
API.listContexts = () => Object.keys ( shortcuts )
|
|
162
|
+
API.listContexts = () => Object.keys ( state.shortcuts )
|
|
163
|
+
|
|
164
|
+
|
|
106
165
|
|
|
107
166
|
/**
|
|
108
167
|
* @function setDependencies
|
|
@@ -110,7 +169,7 @@ function main ( options = {} ) {
|
|
|
110
169
|
* @param {object} deps - Enumerate external dependencies
|
|
111
170
|
* @returns {void}
|
|
112
171
|
*/
|
|
113
|
-
API.setDependencies =
|
|
172
|
+
API.setDependencies = deps => dependencies.extra = { ...dependencies.extra, ...deps }
|
|
114
173
|
|
|
115
174
|
/**
|
|
116
175
|
* @function getDependencies
|
|
@@ -125,24 +184,16 @@ function main ( options = {} ) {
|
|
|
125
184
|
if ( name.startsWith('_') ) inAPI [ name ] = method ( dependencies, state )
|
|
126
185
|
else API [ name ] = method ( dependencies, state )
|
|
127
186
|
})
|
|
128
|
-
|
|
129
|
-
inAPI._listen ()
|
|
187
|
+
|
|
130
188
|
return API
|
|
131
189
|
} // main func.
|
|
132
190
|
|
|
133
191
|
|
|
134
192
|
|
|
135
|
-
main
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
, onShortcut : false // Shortcut log function or false
|
|
141
|
-
, streamKeys : false // Stream keys function or false
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
export default main
|
|
193
|
+
export { main as shortcuts }
|
|
194
|
+
export {
|
|
195
|
+
pluginKey
|
|
196
|
+
, pluginClick
|
|
197
|
+
}
|
|
147
198
|
|
|
148
199
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function _normalizeWithPlugins ( dependencies, state ) {
|
|
2
|
+
/**
|
|
3
|
+
* @function _normalizeWithPlugins
|
|
4
|
+
* @description Function used by plugins during the enable process to normalize the existing and related to the plugin shortcut names.
|
|
5
|
+
* @param {function} _normalizeShortcutName - Plugin internal 'normalize' function.
|
|
6
|
+
* @returns {void}
|
|
7
|
+
*/
|
|
8
|
+
return function _normalizeWithPlugins ( _normalizeShortcutName ) {
|
|
9
|
+
const shortcuts = state.shortcuts;
|
|
10
|
+
Object.keys ( shortcuts ).forEach ( contextName => {
|
|
11
|
+
Object.entries (shortcuts[contextName]).forEach ( ([shortcutName, callbackList]) => {
|
|
12
|
+
const name = _normalizeShortcutName ( shortcutName )
|
|
13
|
+
if ( name !== shortcutName ) {
|
|
14
|
+
delete shortcuts[contextName][shortcutName]
|
|
15
|
+
shortcuts[contextName][name] = callbackList
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}} // _normalizeWithPlugins func.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export default _normalizeWithPlugins
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
function _readShortcutWithPlugins ( dependencies, state ) {
|
|
3
|
+
/**
|
|
4
|
+
* @function _readShortcutWithPlugins
|
|
5
|
+
* @description Searches for belonging plugin and call the plugin method to normalize the shortcut name.
|
|
6
|
+
* @param {string} shortcut - The shortcut to read.
|
|
7
|
+
* @returns {string} - The normalized shortcut name.
|
|
8
|
+
*/
|
|
9
|
+
return function _readShortcutWithPlugins ( shortcut ) {
|
|
10
|
+
const
|
|
11
|
+
{ inAPI } = dependencies
|
|
12
|
+
, pluginName = shortcut.split(':')[0]
|
|
13
|
+
, ix = inAPI._systemAction ( pluginName, 'none' ) // Find a index. Don't call any method.
|
|
14
|
+
;
|
|
15
|
+
let pausedEvent = shortcut;
|
|
16
|
+
if ( ix !== -1 ) pausedEvent = state.plugins[ix].shortcutName ( shortcut )
|
|
17
|
+
return pausedEvent
|
|
18
|
+
}} // _readShortcutWithPlugins func.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export default _readShortcutWithPlugins
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _systemAction ( dependencies, state ) {
|
|
4
|
+
/**
|
|
5
|
+
* @function _systemAction
|
|
6
|
+
* @description Call a specifc plugin method.
|
|
7
|
+
* @param {string} pluginName - The name of the plugin.
|
|
8
|
+
* @param {string} fn - The name of the method to call.
|
|
9
|
+
* @param {any} params - The parameters to pass to the method.
|
|
10
|
+
*/
|
|
11
|
+
return function _systemAction ( pluginName, fn, params=null ) { // Specific plugin command: Mute, unmute, pause, resume, destroy
|
|
12
|
+
return state.plugins.findIndex ( plugin => {
|
|
13
|
+
if ( plugin.getPrefix() === pluginName ) {
|
|
14
|
+
if ( plugin[fn] ) plugin[fn]( params )
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
return false
|
|
18
|
+
})
|
|
19
|
+
}} // _systemAction func.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export default _systemAction
|
|
24
|
+
|
|
25
|
+
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
function changeContext ( dependencies, state ) {
|
|
4
4
|
const
|
|
5
5
|
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
, currentContext
|
|
6
|
+
shortcuts
|
|
7
|
+
, currentContext
|
|
9
8
|
} = state
|
|
10
9
|
, { ev } = dependencies
|
|
11
10
|
;
|
|
12
11
|
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function expose () {
|
|
15
|
+
ev.on ( '*', (...args) => {
|
|
16
|
+
if ( state.exposeShortcut ) state.exposeShortcut ( ...args )
|
|
17
|
+
})
|
|
18
|
+
} // expose func.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* @function changeContext
|
|
15
24
|
* @description Change current context with shortcuts belonging to it.
|
|
@@ -19,14 +28,6 @@ const
|
|
|
19
28
|
return function changeContext ( contextName = false ) {
|
|
20
29
|
const current = currentContext.name;
|
|
21
30
|
|
|
22
|
-
listenOptions.maxSequence = 1
|
|
23
|
-
listenOptions.maxClicks = 1
|
|
24
|
-
|
|
25
|
-
if ( listenOptions.keyIgnore >= 0 ) {
|
|
26
|
-
clearTimeout ( listenOptions.keyIgnore )
|
|
27
|
-
listenOptions.keyIgnore = null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
31
|
if ( !contextName ) { // Switch off all shortcuts if contextName is not defined
|
|
31
32
|
ev.reset ()
|
|
32
33
|
currentContext.name = null
|
|
@@ -34,26 +35,19 @@ return function changeContext ( contextName = false ) {
|
|
|
34
35
|
}
|
|
35
36
|
if ( current === contextName ) return // Do nothing if contextName is the same as current
|
|
36
37
|
if ( !shortcuts [ contextName ] ) { // If contextName is not defined
|
|
37
|
-
ev.emit ( 'shortcuts-error', `Context '${ contextName }' does not exist` )
|
|
38
|
+
ev.emit ( '@shortcuts-error', `Context '${ contextName }' does not exist` )
|
|
38
39
|
return
|
|
39
40
|
}
|
|
40
41
|
if ( shortcuts[current] ) {
|
|
41
42
|
ev.reset () // Disable all shortcuts from current context
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else { // Set key max sequence
|
|
51
|
-
let sequenceArraySize = shortcutName.split(',').length;
|
|
52
|
-
if ( listenOptions.maxSequence < sequenceArraySize ) listenOptions.maxSequence = sequenceArraySize
|
|
53
|
-
}
|
|
54
|
-
list.forEach ( fn => ev.on ( shortcutName, fn ) ) // Enable new context shortcuts
|
|
55
|
-
})
|
|
56
|
-
currentContext.name = contextName
|
|
44
|
+
|
|
45
|
+
currentContext.name = contextName
|
|
46
|
+
state.plugins.forEach ( plugin => plugin.contextChange ( contextName ) ) // Inform plugins for context change
|
|
47
|
+
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => { // Enable new context shortcuts
|
|
48
|
+
list.forEach ( fn => ev.on ( shortcutName, fn ) )
|
|
49
|
+
})
|
|
50
|
+
expose ()
|
|
57
51
|
}} // changeContext func.
|
|
58
52
|
|
|
59
53
|
|
package/src/methods/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import _readMouseEvent from './_readMouseEvent.js'
|
|
5
|
-
import _readShortcut from './_readShortcut.js'
|
|
6
|
-
import _specialChars from './_specialChars.js'
|
|
1
|
+
import _normalizeWithPlugins from './_normalizeWithPlugins.js'
|
|
2
|
+
import _readShortcutWithPlugins from './_readShortcutWithPlugins.js'
|
|
3
|
+
import _systemAction from './_systemAction.js'
|
|
7
4
|
|
|
8
5
|
import load from './load.js'
|
|
9
6
|
import unload from './unload.js'
|
|
@@ -14,13 +11,10 @@ import listShortcuts from './listShortcuts.js'
|
|
|
14
11
|
|
|
15
12
|
export default {
|
|
16
13
|
// Internal methods
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
, _readShortcut
|
|
22
|
-
, _specialChars
|
|
23
|
-
|
|
14
|
+
_normalizeWithPlugins
|
|
15
|
+
, _readShortcutWithPlugins
|
|
16
|
+
, _systemAction
|
|
17
|
+
|
|
24
18
|
// Public methods
|
|
25
19
|
, changeContext
|
|
26
20
|
, listShortcuts
|
package/src/methods/load.js
CHANGED
|
@@ -2,32 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
function load ( dependencies, state ) {
|
|
4
4
|
const
|
|
5
|
-
{ shortcuts } = state
|
|
5
|
+
{ shortcuts, plugins } = state
|
|
6
6
|
, {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} = dependencies;
|
|
7
|
+
API : { changeContext, getContext }
|
|
8
|
+
} = dependencies;
|
|
10
9
|
/**
|
|
11
10
|
* Load a context with shortcuts object
|
|
12
|
-
* @param {Object}
|
|
11
|
+
* @param {Object} shortcutsUpdate - Context description object: { contextName : { shortcut : callback[] }
|
|
13
12
|
* @returns {void}
|
|
14
13
|
*/
|
|
15
|
-
return function load (
|
|
14
|
+
return function load ( shortcutsUpdate ) {
|
|
16
15
|
const
|
|
17
16
|
currentContextName = getContext ()
|
|
18
|
-
,
|
|
17
|
+
, pluginPrefixList = plugins.map ( plugin => plugin.getPrefix().toUpperCase() )
|
|
19
18
|
;
|
|
20
19
|
let hasChanges = false;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if ( contextName === currentContextName ) hasChanges = true;
|
|
20
|
+
|
|
21
|
+
Object.entries ( shortcutsUpdate ).forEach ( ([contextName, contextShortcuts]) => {
|
|
22
|
+
if ( contextName === currentContextName ) hasChanges = true; // If changes in current context will need to reload it
|
|
24
23
|
shortcuts [ contextName ] = {}
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
Object.entries ( contextShortcuts ).forEach ( ([ title, callbackList ]) => {
|
|
25
|
+
let
|
|
26
|
+
name = title
|
|
27
|
+
, test = title.toUpperCase().trim()
|
|
28
|
+
;
|
|
29
|
+
let pluginIndexList = pluginPrefixList.map ( (prefix,i) => test.startsWith ( prefix ) ? i : null ).filter ( i => i !== null );
|
|
30
|
+
if ( pluginIndexList.length ) {
|
|
31
|
+
let id = pluginIndexList[0];
|
|
32
|
+
name = plugins[id].shortcutName ( title )
|
|
33
|
+
}
|
|
27
34
|
if ( callbackList instanceof Function ) callbackList = [ callbackList ]
|
|
28
35
|
shortcuts [contextName][ name ] = callbackList
|
|
29
|
-
}) //
|
|
30
|
-
}) //
|
|
36
|
+
}) // contextShortcuts.forEach
|
|
37
|
+
}) // shortcutsUpdate.forEach
|
|
31
38
|
if ( hasChanges ) { // Reload context shortcuts after loading process if context was active
|
|
32
39
|
changeContext ()
|
|
33
40
|
changeContext ( currentContextName )
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _findTarget ( dependencies, state, target ) {
|
|
4
|
+
|
|
5
|
+
const { listenOptions : {clickTarget}} = state;
|
|
6
|
+
|
|
7
|
+
let t = target;
|
|
8
|
+
if ( t === document ) return null
|
|
9
|
+
if ( t === document.body ) return null
|
|
10
|
+
|
|
11
|
+
if ( t.dataset[clickTarget] ) return t
|
|
12
|
+
if ( t.nodeName === 'A' ) return t
|
|
13
|
+
return _findTarget ( dependencies, state, t.parentNode )
|
|
14
|
+
} // _findTarget func.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export default _findTarget
|
|
19
|
+
|
|
20
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function _listenDOM ( dependencies, state ) {
|
|
5
|
+
const {
|
|
6
|
+
ev
|
|
7
|
+
, _findTarget
|
|
8
|
+
, _readClickEvent
|
|
9
|
+
, mainDependencies
|
|
10
|
+
} = dependencies
|
|
11
|
+
const { listenOptions, currentContext } = state
|
|
12
|
+
const { mouseWait } = listenOptions
|
|
13
|
+
|
|
14
|
+
let
|
|
15
|
+
mouseTarget = null // Dom element or null
|
|
16
|
+
, mouseDomEvent = null
|
|
17
|
+
, mouseTimer = null // Timer for mouse sequence or null
|
|
18
|
+
, mouseIgnore = null // Timer for ignoring mouse clicks or null
|
|
19
|
+
, count = 0
|
|
20
|
+
;
|
|
21
|
+
|
|
22
|
+
function mouseSequenceEnd () { // Execute when mouse sequence ends
|
|
23
|
+
const
|
|
24
|
+
mouseEvent = _readClickEvent ( mouseDomEvent, count )
|
|
25
|
+
, data = {
|
|
26
|
+
target : mouseTarget
|
|
27
|
+
, targetProps : mouseTarget ? mouseTarget.getBoundingClientRect() : null
|
|
28
|
+
, x : mouseDomEvent.clientX
|
|
29
|
+
, y : mouseDomEvent.clientY
|
|
30
|
+
, context : currentContext.name
|
|
31
|
+
, note : currentContext.note
|
|
32
|
+
, event : mouseDomEvent
|
|
33
|
+
, dependencies : mainDependencies.extra
|
|
34
|
+
, type : 'click'
|
|
35
|
+
}
|
|
36
|
+
;
|
|
37
|
+
|
|
38
|
+
ev.emit ( mouseEvent, data )
|
|
39
|
+
// Reset:
|
|
40
|
+
mouseTimer = null
|
|
41
|
+
mouseIgnore = null
|
|
42
|
+
mouseTarget = null
|
|
43
|
+
mouseDomEvent = null
|
|
44
|
+
count = 0
|
|
45
|
+
} // mouseSequenceEnd func.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
function listenLeftClick ( event ) {
|
|
50
|
+
let targetMax = listenOptions.maxClicks; // Maximum number of clicks per target
|
|
51
|
+
clearTimeout ( mouseTimer )
|
|
52
|
+
if ( mouseIgnore ) {
|
|
53
|
+
clearTimeout ( mouseIgnore )
|
|
54
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
mouseTarget = _findTarget ( dependencies, state, event.target )
|
|
58
|
+
if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
|
|
59
|
+
if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
|
|
60
|
+
mouseDomEvent = event
|
|
61
|
+
count++
|
|
62
|
+
if ( count >= targetMax ) {
|
|
63
|
+
mouseSequenceEnd ()
|
|
64
|
+
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
68
|
+
} // listenLeftClick func.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
function listenRightClick ( event ) {
|
|
73
|
+
let targetMax = listenOptions.maxClicks; // Maximum number of clicks per target
|
|
74
|
+
clearTimeout ( mouseTimer )
|
|
75
|
+
if ( mouseIgnore ) {
|
|
76
|
+
clearTimeout ( mouseIgnore )
|
|
77
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
mouseTarget = _findTarget ( dependencies, state, event.target )
|
|
81
|
+
if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
|
|
82
|
+
if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
|
|
83
|
+
mouseDomEvent = event
|
|
84
|
+
count++
|
|
85
|
+
if ( count >= targetMax ) {
|
|
86
|
+
mouseSequenceEnd ()
|
|
87
|
+
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
91
|
+
} // listenRightClick func.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
function start () {
|
|
97
|
+
if ( state.active ) return
|
|
98
|
+
window.addEventListener ( 'contextmenu', listenRightClick )
|
|
99
|
+
document.addEventListener ( 'click' , listenLeftClick )
|
|
100
|
+
state.active = true
|
|
101
|
+
} // start func.
|
|
102
|
+
|
|
103
|
+
function stop () {
|
|
104
|
+
if ( !state.active ) return
|
|
105
|
+
window.removeEventListener ( 'contextmenu', listenRightClick )
|
|
106
|
+
document.removeEventListener ( 'click' , listenLeftClick )
|
|
107
|
+
state.active = false
|
|
108
|
+
} // stop func.
|
|
109
|
+
|
|
110
|
+
return { start, stop }
|
|
111
|
+
} // _listenDOM func.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
export default _listenDOM
|
|
116
|
+
|
|
117
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _normalizeShortcutName ( name ) {
|
|
4
|
+
const
|
|
5
|
+
upperCase = name.toUpperCase ()
|
|
6
|
+
, regex = /CLICK\s*\:/i
|
|
7
|
+
, isClickShortcut = regex.test(upperCase)
|
|
8
|
+
, mouseNames = [ 'LEFT', 'MIDDLE', 'RIGHT' ]
|
|
9
|
+
, modifiers = [ 'ALT', 'SHIFT', 'CTRL' ]
|
|
10
|
+
;
|
|
11
|
+
let
|
|
12
|
+
btn = null
|
|
13
|
+
, usedModifiers = []
|
|
14
|
+
, counter = 0
|
|
15
|
+
, sliceIndex = upperCase.indexOf ( ':' )
|
|
16
|
+
;
|
|
17
|
+
|
|
18
|
+
// Click event format: CLICK:LEFT-2-ALT-SHIFT-CTRL
|
|
19
|
+
|
|
20
|
+
if ( !isClickShortcut ) return name
|
|
21
|
+
let shortcutArray = upperCase.slice(sliceIndex+1).trim().split('-').map ( x => x.trim() );
|
|
22
|
+
shortcutArray.forEach ( item => {
|
|
23
|
+
if ( mouseNames.includes ( item )) {
|
|
24
|
+
btn = item
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
if ( modifiers.includes ( item )) {
|
|
28
|
+
usedModifiers.push ( item )
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
if ( !isNaN ( item )) {
|
|
32
|
+
counter = item
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
}) // forEach
|
|
36
|
+
|
|
37
|
+
return `CLICK:${btn}-${counter}${usedModifiers.length>0?'-':''}${usedModifiers.sort().join('-')}`
|
|
38
|
+
} // _normalizeShortcutName func.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export default _normalizeShortcutName
|
|
43
|
+
|
|
44
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _readClickEvent ( event, count ) {
|
|
4
|
+
let
|
|
5
|
+
{ shiftKey, altKey, ctrlKey, key, button } = event
|
|
6
|
+
, mouseNames = [ 'LEFT', 'MIDDLE', 'RIGHT' ]
|
|
7
|
+
, mouseEvent = `CLICK:${mouseNames[button]}-${count}`
|
|
8
|
+
, mods = []
|
|
9
|
+
;
|
|
10
|
+
|
|
11
|
+
// res.push ( mouseEvent )
|
|
12
|
+
if ( ctrlKey ) mods.push ( 'CTRL' )
|
|
13
|
+
if ( shiftKey ) mods.push ( 'SHIFT' )
|
|
14
|
+
if ( altKey ) mods.push ( 'ALT' )
|
|
15
|
+
|
|
16
|
+
if ( mods.length > 0 ) return `${mouseEvent}${mods.length>0?'-':''}${mods.sort().join('-')}`
|
|
17
|
+
else return `${mouseEvent}`
|
|
18
|
+
} // _readClickEvent func.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export default _readClickEvent
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
|
|
14
|
+
if ( contextName == null ) return 0
|
|
15
|
+
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => { // Enable new context shortcuts and set a listenOptions 'maxSequence'
|
|
16
|
+
let isClickEv = regex.test ( shortcutName );
|
|
17
|
+
if ( !isClickEv ) return
|
|
18
|
+
count++
|
|
19
|
+
|
|
20
|
+
let [ ,numberClicks ] = shortcutName.slice(6).split('-');
|
|
21
|
+
if ( listenOptions.maxClicks < numberClicks ) listenOptions.maxClicks = numberClicks
|
|
22
|
+
})
|
|
23
|
+
return count
|
|
24
|
+
} // _registerShortcutEvents func.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export default _registerShortcutEvents
|
|
29
|
+
|
|
30
|
+
|