@peter.naydenov/shortcuts 3.5.1 → 4.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/API.md +939 -0
- package/CODE_OF_CONDUCT.md +84 -0
- package/CONTRIBUTING.md +476 -0
- package/Changelog.md +30 -1
- package/How.to.create.plugins.md +929 -0
- package/Migration.guide.md +48 -0
- package/README.md +396 -24
- package/dist/main.d.ts +54 -2
- package/dist/methods/_normalizeWithPlugins.d.ts +63 -1
- package/dist/methods/_readShortcutWithPlugins.d.ts +8 -1
- package/dist/methods/_setupPlugin.d.ts +9 -0
- package/dist/methods/_systemAction.d.ts +8 -1
- package/dist/methods/changeContext.d.ts +8 -1
- package/dist/methods/index.d.ts +2 -0
- package/dist/methods/listShortcuts.d.ts +1 -16
- package/dist/methods/load.d.ts +8 -1
- package/dist/methods/unload.d.ts +8 -1
- package/dist/plugins/click/_findTarget.d.ts +9 -1
- package/dist/plugins/click/_listenDOM.d.ts +76 -3
- package/dist/plugins/click/_normalizeShortcutName.d.ts +7 -1
- package/dist/plugins/click/_registerShortcutEvents.d.ts +26 -0
- package/dist/plugins/click/index.d.ts +6 -5
- package/dist/plugins/form/_defaults.d.ts +13 -1
- package/dist/plugins/form/_listenDOM.d.ts +66 -3
- package/dist/plugins/form/_registerShortcutEvents.d.ts +95 -1
- package/dist/plugins/form/index.d.ts +2 -3
- package/dist/plugins/hover/_findTarget.d.ts +10 -0
- package/dist/plugins/hover/_listenDOM.d.ts +68 -0
- package/dist/plugins/hover/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/hover/_registerShortcutEvents.d.ts +28 -0
- package/dist/plugins/hover/index.d.ts +14 -0
- package/dist/plugins/key/_listenDOM.d.ts +61 -3
- package/dist/plugins/key/_registerShortcutEvents.d.ts +26 -0
- package/dist/plugins/key/_specialChars.d.ts +6 -31
- package/dist/plugins/key/index.d.ts +2 -3
- package/dist/plugins/scroll/_listenDOM.d.ts +58 -0
- package/dist/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
- package/dist/plugins/scroll/index.d.ts +16 -0
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/eslint.config.js +80 -0
- package/html/assets/index-COTh6lXR.css +1 -0
- package/html/assets/index-DOkKC3NI.js +53 -0
- package/html/bg.png +0 -0
- package/html/favicon.ico +0 -0
- package/html/favicon.svg +5 -0
- package/html/html.meta.json.gz +0 -0
- package/html/index.html +32 -0
- package/package.json +16 -12
- package/shortcuts.png +0 -0
- package/src/main.js +52 -22
- package/src/methods/_normalizeWithPlugins.js +26 -2
- package/src/methods/_readShortcutWithPlugins.js +9 -2
- package/src/methods/_setupPlugin.js +93 -0
- package/src/methods/_systemAction.js +12 -4
- package/src/methods/changeContext.js +11 -3
- package/src/methods/index.js +2 -0
- package/src/methods/listShortcuts.js +5 -12
- package/src/methods/load.js +11 -4
- package/src/methods/unload.js +8 -1
- package/src/plugins/click/_findTarget.js +11 -5
- package/src/plugins/click/_listenDOM.js +58 -20
- package/src/plugins/click/_normalizeShortcutName.js +11 -4
- package/src/plugins/click/_readClickEvent.js +1 -1
- package/src/plugins/click/_registerShortcutEvents.js +33 -5
- package/src/plugins/click/index.js +34 -51
- package/src/plugins/form/_defaults.js +13 -3
- package/src/plugins/form/_listenDOM.js +46 -9
- package/src/plugins/form/_normalizeShortcutName.js +2 -2
- package/src/plugins/form/_registerShortcutEvents.js +93 -17
- package/src/plugins/form/index.js +26 -47
- package/src/plugins/hover/_findTarget.js +26 -0
- package/src/plugins/hover/_listenDOM.js +154 -0
- package/src/plugins/hover/_normalizeShortcutName.js +21 -0
- package/src/plugins/hover/_registerShortcutEvents.js +51 -0
- package/src/plugins/hover/index.js +71 -0
- package/src/plugins/key/_listenDOM.js +67 -33
- package/src/plugins/key/_normalizeShortcutName.js +4 -3
- package/src/plugins/key/_readKeyEvent.js +1 -1
- package/src/plugins/key/_registerShortcutEvents.js +34 -5
- package/src/plugins/key/_specialChars.js +5 -0
- package/src/plugins/key/index.js +35 -50
- package/src/plugins/scroll/_listenDOM.js +141 -0
- package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
- package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
- package/src/plugins/scroll/index.js +61 -0
- package/test/01-general.test.js +92 -23
- package/test/02-key.test.js +241 -40
- package/test/03-click.test.js +291 -47
- package/test/04-form.test.js +241 -47
- package/test/05-hover.test.js +463 -0
- package/test/06-scroll.test.js +374 -0
- package/test-helpers/Block.jsx +3 -2
- package/test-helpers/style.css +6 -1
- package/vitest.config.js +13 -11
- package/How..to.make.plugins.md +0 -41
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @function _listenDOM
|
|
6
|
+
* @description Set up DOM event listeners for click events
|
|
7
|
+
* @param {Object} dependencies - Dependencies object containing ev, _findTarget, _readClickEvent, extra, resetState
|
|
8
|
+
* @param {Object} state - Plugin state containing listenOptions and currentContext
|
|
9
|
+
* @returns {Object} - Object containing start and stop methods
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} ClickEventData
|
|
12
|
+
* @property {Element} target - The DOM element that was clicked
|
|
13
|
+
* @property {number} x - X coordinate of the click event
|
|
14
|
+
* @property {number} y - Y coordinate of the click event
|
|
15
|
+
* @property {string} context - Current context name
|
|
16
|
+
* @property {string|null} note - Current context note
|
|
17
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
18
|
+
* @property {Event} event - The original DOM event
|
|
19
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
20
|
+
* @property {Object} viewport - Viewport information with X, Y, width, height
|
|
21
|
+
* @property {Object} sizes - Element dimensions with width, height
|
|
22
|
+
* @property {Object} position - Element position relative to viewport with x, y
|
|
23
|
+
* @property {Object} pagePosition - Element position relative to page with x, y
|
|
24
|
+
* @property {string} type - Event type ('click')
|
|
25
|
+
*/
|
|
4
26
|
function _listenDOM ( dependencies, state ) {
|
|
5
27
|
const {
|
|
6
28
|
ev
|
|
7
29
|
, _findTarget
|
|
8
30
|
, _readClickEvent
|
|
9
|
-
,
|
|
31
|
+
, extra
|
|
10
32
|
} = dependencies
|
|
11
33
|
const { listenOptions, currentContext } = state
|
|
12
|
-
const { mouseWait } = listenOptions
|
|
13
34
|
|
|
14
35
|
let
|
|
15
36
|
mouseTarget = null // Dom element or null
|
|
@@ -20,17 +41,33 @@ function _listenDOM ( dependencies, state ) {
|
|
|
20
41
|
;
|
|
21
42
|
|
|
22
43
|
function mouseSequenceEnd () { // Execute when mouse sequence ends
|
|
44
|
+
if ( !mouseTarget ) return // No valid target found
|
|
23
45
|
const
|
|
46
|
+
{ left, top, width, height } = mouseTarget.getBoundingClientRect ()
|
|
47
|
+
, scrollX = window.scrollX
|
|
48
|
+
, scrollY = window.scrollY
|
|
49
|
+
;
|
|
50
|
+
|
|
51
|
+
const
|
|
24
52
|
mouseEvent = _readClickEvent ( mouseDomEvent, count )
|
|
25
|
-
, data = {
|
|
53
|
+
, data = {
|
|
26
54
|
target : mouseTarget
|
|
27
|
-
, targetProps : mouseTarget ? mouseTarget.getBoundingClientRect() : null
|
|
28
55
|
, x : mouseDomEvent.clientX
|
|
29
56
|
, y : mouseDomEvent.clientY
|
|
30
57
|
, context : currentContext.name
|
|
31
58
|
, note : currentContext.note
|
|
59
|
+
, options : state.listenOptions
|
|
32
60
|
, event : mouseDomEvent
|
|
33
|
-
, dependencies :
|
|
61
|
+
, dependencies : extra
|
|
62
|
+
, viewport : { // Viewport scroll positions and sizes
|
|
63
|
+
X:scrollX
|
|
64
|
+
, Y:scrollY
|
|
65
|
+
, width:window.innerWidth
|
|
66
|
+
, height:window.innerHeight
|
|
67
|
+
}
|
|
68
|
+
, sizes : { width, height } // Element sizes
|
|
69
|
+
, position : { x:left, y:top } // Position relative to viewport
|
|
70
|
+
, pagePosition : { x:left+scrollX, y:top+scrollY } // Position relative to page
|
|
34
71
|
, type : 'click'
|
|
35
72
|
}
|
|
36
73
|
;
|
|
@@ -47,47 +84,49 @@ function _listenDOM ( dependencies, state ) {
|
|
|
47
84
|
|
|
48
85
|
|
|
49
86
|
function listenLeftClick ( event ) {
|
|
50
|
-
let targetMax =
|
|
87
|
+
let targetMax = state.maxLeftClicks; // Maximum number of clicks per target
|
|
51
88
|
clearTimeout ( mouseTimer )
|
|
52
89
|
if ( mouseIgnore ) {
|
|
53
90
|
clearTimeout ( mouseIgnore )
|
|
54
|
-
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
91
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, listenOptions.mouseWait )
|
|
55
92
|
return
|
|
56
93
|
}
|
|
57
94
|
mouseTarget = _findTarget ( dependencies, state, event.target )
|
|
95
|
+
if ( mouseTarget == null ) return
|
|
58
96
|
if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
|
|
59
97
|
if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
|
|
60
98
|
mouseDomEvent = event
|
|
61
99
|
count++
|
|
62
100
|
if ( count >= targetMax ) {
|
|
63
101
|
mouseSequenceEnd ()
|
|
64
|
-
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
102
|
+
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, listenOptions.mouseWait )
|
|
65
103
|
return
|
|
66
104
|
}
|
|
67
|
-
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
105
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, listenOptions.mouseWait )
|
|
68
106
|
} // listenLeftClick func.
|
|
69
107
|
|
|
70
108
|
|
|
71
109
|
|
|
72
110
|
function listenRightClick ( event ) {
|
|
73
|
-
let targetMax =
|
|
111
|
+
let targetMax = state.maxRightClicks; // Maximum number of clicks per target
|
|
74
112
|
clearTimeout ( mouseTimer )
|
|
75
113
|
if ( mouseIgnore ) {
|
|
76
114
|
clearTimeout ( mouseIgnore )
|
|
77
|
-
mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
115
|
+
mouseIgnore = setTimeout ( () => mouseIgnore=null, listenOptions.mouseWait )
|
|
78
116
|
return
|
|
79
117
|
}
|
|
80
118
|
mouseTarget = _findTarget ( dependencies, state, event.target )
|
|
119
|
+
if ( mouseTarget == null ) return
|
|
81
120
|
if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
|
|
82
121
|
if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
|
|
83
122
|
mouseDomEvent = event
|
|
84
123
|
count++
|
|
85
|
-
if ( count >= targetMax ) {
|
|
124
|
+
if ( count >= targetMax ) {
|
|
86
125
|
mouseSequenceEnd ()
|
|
87
|
-
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
|
|
126
|
+
if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, listenOptions.mouseWait )
|
|
88
127
|
return
|
|
89
128
|
}
|
|
90
|
-
mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
|
|
129
|
+
mouseTimer = setTimeout ( mouseSequenceEnd, listenOptions.mouseWait )
|
|
91
130
|
} // listenRightClick func.
|
|
92
131
|
|
|
93
132
|
|
|
@@ -105,15 +144,14 @@ function _listenDOM ( dependencies, state ) {
|
|
|
105
144
|
window.removeEventListener ( 'contextmenu', listenRightClick )
|
|
106
145
|
document.removeEventListener ( 'click' , listenLeftClick )
|
|
107
146
|
state.active = false
|
|
108
|
-
|
|
109
|
-
if ( mouseTimer ) {
|
|
110
|
-
clearTimeout ( mouseTimer )
|
|
111
|
-
mouseTimer = null
|
|
112
|
-
}
|
|
147
|
+
|
|
113
148
|
if ( mouseIgnore ) {
|
|
114
149
|
clearTimeout ( mouseIgnore )
|
|
115
150
|
mouseIgnore = null
|
|
116
|
-
|
|
151
|
+
}
|
|
152
|
+
// Reset all state variables to prevent pollution between tests
|
|
153
|
+
mouseTarget = null
|
|
154
|
+
mouseDomEvent = null
|
|
117
155
|
count = 0
|
|
118
156
|
} // stop func.
|
|
119
157
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @function _normalizeShortcutName
|
|
5
|
+
* @description Normalize click shortcut name to standard format
|
|
6
|
+
* @param {string} name - Raw shortcut name
|
|
7
|
+
* @returns {string} - Normalized shortcut name
|
|
8
|
+
*/
|
|
3
9
|
function _normalizeShortcutName ( name ) {
|
|
4
10
|
const
|
|
5
11
|
upperCase = name.toUpperCase ()
|
|
@@ -10,15 +16,16 @@ function _normalizeShortcutName ( name ) {
|
|
|
10
16
|
;
|
|
11
17
|
let
|
|
12
18
|
btn = null
|
|
13
|
-
, usedModifiers = []
|
|
14
19
|
, counter = 0
|
|
20
|
+
; const usedModifiers = []
|
|
15
21
|
, sliceIndex = upperCase.indexOf ( ':' )
|
|
16
22
|
;
|
|
17
23
|
|
|
18
24
|
// Click event format: CLICK:LEFT-2-ALT-SHIFT-CTRL
|
|
19
25
|
|
|
20
|
-
if ( !isClickShortcut
|
|
21
|
-
|
|
26
|
+
if ( !isClickShortcut ) return name
|
|
27
|
+
if ( upperCase.includes('SETUP') ) return 'CLICK:SETUP'
|
|
28
|
+
const shortcutArray = upperCase.slice(sliceIndex+1).trim().split('-').map ( x => x.trim() );
|
|
22
29
|
shortcutArray.forEach ( item => {
|
|
23
30
|
if ( mouseNames.includes ( item )) {
|
|
24
31
|
btn = item
|
|
@@ -33,7 +40,7 @@ function _normalizeShortcutName ( name ) {
|
|
|
33
40
|
return
|
|
34
41
|
}
|
|
35
42
|
}) // forEach
|
|
36
|
-
|
|
43
|
+
|
|
37
44
|
return `CLICK:${btn}-${counter}${usedModifiers.length>0?'-':''}${usedModifiers.sort().join('-')}`
|
|
38
45
|
} // _normalizeShortcutName func.
|
|
39
46
|
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @function _registerShortcutEvents
|
|
5
|
+
* @description Register click shortcut events and handle setup
|
|
6
|
+
* @param {Object} dependencies - Dependencies object containing regex
|
|
7
|
+
* @param {Object} pluginState - Plugin state containing listenOptions, currentContext, shortcuts, etc.
|
|
8
|
+
* @returns {number} - Number of registered shortcuts
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} ClickSetupData
|
|
11
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
12
|
+
* @property {Object} defaults - Default options (clone of pluginState.defaultOptions)
|
|
13
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
14
|
+
*/
|
|
3
15
|
function _registerShortcutEvents ( dependencies, pluginState ) {
|
|
4
16
|
let count = 0;
|
|
17
|
+
let hasSetup = false;
|
|
18
|
+
const df = pluginState.defaultOptions;
|
|
5
19
|
const
|
|
6
20
|
{ regex } = dependencies
|
|
7
21
|
, {
|
|
@@ -11,15 +25,29 @@ const
|
|
|
11
25
|
} = pluginState
|
|
12
26
|
;
|
|
13
27
|
|
|
14
|
-
if ( contextName == null ) return
|
|
28
|
+
if ( contextName == null ) return count
|
|
15
29
|
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => { // Enable new context shortcuts and set a listenOptions 'maxSequence'
|
|
16
|
-
|
|
30
|
+
const isClickEv = regex.test ( shortcutName );
|
|
17
31
|
if ( !isClickEv ) return
|
|
32
|
+
if ( shortcutName === 'CLICK:SETUP' ) {
|
|
33
|
+
hasSetup = true
|
|
34
|
+
const updateOptions = list.reduce ( ( res, fn ) => {
|
|
35
|
+
const r = fn ({
|
|
36
|
+
dependencies : dependencies.extra,
|
|
37
|
+
defaults : structuredClone(pluginState.defaultOptions),
|
|
38
|
+
options : listenOptions
|
|
39
|
+
})
|
|
40
|
+
return Object.assign ( res, r )
|
|
41
|
+
}, df )
|
|
42
|
+
Object.assign ( pluginState.listenOptions, updateOptions )
|
|
43
|
+
return
|
|
44
|
+
}
|
|
18
45
|
count++
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
46
|
+
const [ button,numberClicks ] = shortcutName.slice(6).split('-');
|
|
47
|
+
if ( button === 'LEFT' && pluginState.maxLeftClicks < numberClicks ) pluginState.maxLeftClicks = numberClicks
|
|
48
|
+
if ( button === 'RIGHT' && pluginState.maxRightClicks < numberClicks ) pluginState.maxRightClicks = numberClicks
|
|
22
49
|
})
|
|
50
|
+
if ( !hasSetup ) Object.assign ( pluginState.listenOptions, df )
|
|
23
51
|
return count
|
|
24
52
|
} // _registerShortcutEvents func.
|
|
25
53
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
|
|
3
5
|
import _findTarget from "./_findTarget"
|
|
4
6
|
import _listenDOM from "./_listenDOM"
|
|
5
7
|
import _normalizeShortcutName from "./_normalizeShortcutName"
|
|
@@ -12,69 +14,50 @@ import _registerShortcutEvents from "./_registerShortcutEvents"
|
|
|
12
14
|
/**
|
|
13
15
|
* @function pluginClick
|
|
14
16
|
* @description Plugin for mouse click shortcuts
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {Object} state - Library state
|
|
17
|
+
* @param {function} setupPlugin - Plugin setup function from the library
|
|
17
18
|
* @param {Object} [options={}] - Plugin options
|
|
18
19
|
* @param {number} [options.mouseWait=320] - Time to wait for click sequence in ms
|
|
19
|
-
* @param {string} [options.clickTarget='click'] -
|
|
20
|
+
* @param {string[]} [options.clickTarget=['data-click', 'href']] - Array of attribute names for click targets
|
|
21
|
+
* @param {function} [options.streamKeys] - Function to stream key presses
|
|
20
22
|
* @returns {PluginAPI} Plugin API
|
|
21
23
|
*/
|
|
22
|
-
function pluginClick (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
, deps = {
|
|
27
|
-
ev: dependencies.ev
|
|
28
|
-
, _findTarget
|
|
24
|
+
function pluginClick ( setupPlugin, options = {}) {
|
|
25
|
+
const
|
|
26
|
+
deps = {
|
|
27
|
+
_findTarget
|
|
29
28
|
, _readClickEvent
|
|
30
|
-
, mainDependencies : dependencies
|
|
31
29
|
, regex : /CLICK\s*\:/i
|
|
32
30
|
}
|
|
33
31
|
, pluginState = {
|
|
34
|
-
|
|
35
|
-
,
|
|
36
|
-
,
|
|
32
|
+
active : false
|
|
33
|
+
, maxLeftClicks : 1 // How many clicks can be pressed in a sequence. Controlled automatically by '_registerShortcutEvents' function.
|
|
34
|
+
, maxRightClicks: 1 // How many right clicks can be pressed in a sequence. Controlled automatically by '_registerShortcutEvents' function.
|
|
35
|
+
, defaultOptions : {
|
|
36
|
+
mouseWait : 320 // 320 ms
|
|
37
|
+
, clickTarget : ['data-click', 'href' ] // Attribute names as click targets
|
|
38
|
+
}
|
|
37
39
|
, listenOptions : {
|
|
38
|
-
mouseWait :
|
|
39
|
-
,
|
|
40
|
-
, clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
|
|
40
|
+
mouseWait : 320 // 320 ms
|
|
41
|
+
, clickTarget : [ 'data-click', 'href' ] // Attribute names as click targets
|
|
41
42
|
}
|
|
43
|
+
, streamKeys : (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false // Keyboard stream function
|
|
42
44
|
} // pluginState
|
|
43
|
-
;
|
|
45
|
+
;
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
, contextChange : () => {
|
|
61
|
-
countShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
62
|
-
if ( countShortcuts < 1 ) { // Remove DOM listener if there are no shortcuts in the current context
|
|
63
|
-
mouseListener.stop ()
|
|
64
|
-
}
|
|
65
|
-
if ( countShortcuts > 0 ) { // Add DOM listener if there are shortcuts in the current context
|
|
66
|
-
mouseListener.start ()
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
, mute : () => {
|
|
70
|
-
|
|
71
|
-
mouseListener.stop ()
|
|
72
|
-
}
|
|
73
|
-
, unmute : () => mouseListener.start ()
|
|
74
|
-
, destroy : () => mouseListener.stop ()
|
|
75
|
-
}; // pluginAPI
|
|
76
|
-
Object.freeze ( pluginAPI )
|
|
77
|
-
return pluginAPI
|
|
47
|
+
function resetState () {
|
|
48
|
+
// TODO: No reset available at the moment
|
|
49
|
+
} // resetState func.
|
|
50
|
+
deps.resetState = resetState
|
|
51
|
+
|
|
52
|
+
return setupPlugin ({
|
|
53
|
+
prefix: 'click'
|
|
54
|
+
, _normalizeShortcutName
|
|
55
|
+
, _registerShortcutEvents
|
|
56
|
+
, _listenDOM
|
|
57
|
+
|
|
58
|
+
, pluginState
|
|
59
|
+
, deps
|
|
60
|
+
})
|
|
78
61
|
} // pluginClick func.
|
|
79
62
|
|
|
80
63
|
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} _defaults
|
|
3
|
+
* @property {function} watch - Function that returns CSS selector for form elements to watch
|
|
4
|
+
* @property {function} define - Function that determines the type of form element
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @const {_defaults}
|
|
9
|
+
* @description Default configuration for form plugin
|
|
10
|
+
*/
|
|
1
11
|
const _defaults = {
|
|
2
12
|
watch : () => 'input, select, textarea, button, a'
|
|
3
|
-
, define: (
|
|
4
|
-
if (
|
|
13
|
+
, define: ({ target }) => {
|
|
14
|
+
if ( target.type === 'checkbox' || target.type === 'radio' ) {
|
|
5
15
|
return 'checkbox'
|
|
6
16
|
}
|
|
7
|
-
if (
|
|
17
|
+
if ( target.type == 'button' || target.type=='submit' ) {
|
|
8
18
|
return 'button'
|
|
9
19
|
}
|
|
10
20
|
return 'input'
|
|
@@ -2,18 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @function _listenDOM
|
|
7
|
+
* @description Set up DOM event listeners for form events
|
|
8
|
+
* @param {Object} dependencies - Dependencies object containing ev
|
|
9
|
+
* @param {Object} state - Plugin state containing listenOptions and currentContext
|
|
10
|
+
* @returns {Object} - Object containing start and stop methods
|
|
11
|
+
*
|
|
12
|
+
* @typedef {Object} FormEventData
|
|
13
|
+
* @property {Element} target - The DOM element that triggered the form event
|
|
14
|
+
* @property {string} context - Current context name
|
|
15
|
+
* @property {string|null} note - Current context note
|
|
16
|
+
* @property {Event} event - The original DOM event
|
|
17
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
18
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
19
|
+
* @property {Object} viewport - Viewport information with X, Y, width, height
|
|
20
|
+
* @property {Object} sizes - Element dimensions with width, height
|
|
21
|
+
* @property {Object} position - Element position relative to viewport with x, y
|
|
22
|
+
* @property {Object} pagePosition - Element position relative to page with x, y
|
|
23
|
+
* @property {string} type - Event type ('form-in', 'form-out', 'form-instant')
|
|
24
|
+
*/
|
|
5
25
|
function _listenDOM ( dependencies, state ) {
|
|
6
26
|
const { ev } = dependencies;
|
|
7
27
|
let timeout = null;
|
|
8
28
|
|
|
9
29
|
function setupData ( dependencies, state, event, type) {
|
|
30
|
+
const
|
|
31
|
+
{ left, top, width, height } = event.target.getBoundingClientRect ()
|
|
32
|
+
, scrollX = window.scrollX
|
|
33
|
+
, scrollY = window.scrollY
|
|
34
|
+
;
|
|
10
35
|
return {
|
|
11
36
|
target : event.target
|
|
12
|
-
// TODO: Find if is possible to add some size and positioning data
|
|
13
37
|
, context : state.currentContext.name
|
|
14
38
|
, note : state.currentContext.note
|
|
15
39
|
, event
|
|
16
|
-
, dependencies : dependencies.
|
|
40
|
+
, dependencies : dependencies.extra
|
|
41
|
+
, options : state.listenOptions
|
|
42
|
+
, viewport : { // Viewport scroll positions and sizes
|
|
43
|
+
X:scrollX
|
|
44
|
+
, Y:scrollY
|
|
45
|
+
, width:window.innerWidth
|
|
46
|
+
, height:window.innerHeight
|
|
47
|
+
}
|
|
48
|
+
, sizes : { width, height } // Element sizes
|
|
49
|
+
, position : { x:left, y:top } // Position relative to viewport
|
|
50
|
+
, pagePosition : { x:left+scrollX, y:top+scrollY } // Position relative to page
|
|
17
51
|
, type
|
|
18
52
|
}
|
|
19
53
|
} // setupData func.
|
|
@@ -22,8 +56,8 @@ function _listenDOM ( dependencies, state ) {
|
|
|
22
56
|
const
|
|
23
57
|
{ callbacks, typeFn } = state
|
|
24
58
|
, target = event.target
|
|
25
|
-
,
|
|
26
|
-
,
|
|
59
|
+
, prop = setupData ( dependencies, state, event, "form-in" )
|
|
60
|
+
, type = typeFn ( prop )
|
|
27
61
|
, key = `${type}/in`
|
|
28
62
|
;
|
|
29
63
|
if ( callbacks[key] == null ) return
|
|
@@ -34,8 +68,7 @@ function _listenDOM ( dependencies, state ) {
|
|
|
34
68
|
const
|
|
35
69
|
{ callbacks, typeFn } = state
|
|
36
70
|
, prop = setupData ( dependencies, state, event, "form-out" )
|
|
37
|
-
,
|
|
38
|
-
, type = typeFn ( target )
|
|
71
|
+
, type = typeFn ( prop )
|
|
39
72
|
, key = `${type}/out`
|
|
40
73
|
;
|
|
41
74
|
if ( callbacks[key] == null ) return
|
|
@@ -47,8 +80,7 @@ function _listenDOM ( dependencies, state ) {
|
|
|
47
80
|
const
|
|
48
81
|
{ callbacks, typeFn } = state
|
|
49
82
|
, prop = setupData ( dependencies, state, event, "form-instant" )
|
|
50
|
-
,
|
|
51
|
-
, type = typeFn ( target )
|
|
83
|
+
, type = typeFn ( prop )
|
|
52
84
|
, wait = state.wait[`${type}`]
|
|
53
85
|
, key = `${type}/instant`
|
|
54
86
|
;
|
|
@@ -57,7 +89,7 @@ function _listenDOM ( dependencies, state ) {
|
|
|
57
89
|
ev.emit ( key, prop, callbacks[key] )
|
|
58
90
|
return
|
|
59
91
|
}
|
|
60
|
-
|
|
92
|
+
const fn = () => ev.emit ( key, prop, callbacks[key] )
|
|
61
93
|
clearTimeout ( timeout )
|
|
62
94
|
timeout = setTimeout ( fn, wait )
|
|
63
95
|
} // input func.
|
|
@@ -78,6 +110,11 @@ function _listenDOM ( dependencies, state ) {
|
|
|
78
110
|
document.removeEventListener ( 'focusout', listenFocusOut );
|
|
79
111
|
document.removeEventListener ( 'input', listenInput );
|
|
80
112
|
state.active = false
|
|
113
|
+
// Clear any pending timeout to prevent state pollution between tests
|
|
114
|
+
if ( timeout ) {
|
|
115
|
+
clearTimeout ( timeout )
|
|
116
|
+
timeout = null
|
|
117
|
+
}
|
|
81
118
|
} // stop func.
|
|
82
119
|
|
|
83
120
|
return { start, stop }
|
|
@@ -9,9 +9,9 @@ function _normalizeShortcutName ( name ) {
|
|
|
9
9
|
;
|
|
10
10
|
|
|
11
11
|
if ( !isKeyboardShortcut ) return name
|
|
12
|
-
|
|
12
|
+
const shortcut = upperCase.slice(sliceIndex+1).trim ()
|
|
13
13
|
|
|
14
|
-
return `FORM:${shortcut}`
|
|
14
|
+
return `FORM:${shortcut}`
|
|
15
15
|
} // _normalizeShortcutName func.
|
|
16
16
|
|
|
17
17
|
|