@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,52 +1,128 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @function _registerShortcutEvents
|
|
5
|
+
* @description Register form shortcut events and handle setup
|
|
6
|
+
* @param {Object} dependencies - Dependencies object containing regex, _defaults, ev
|
|
7
|
+
* @param {Object} pluginState - Plugin state containing currentContext, shortcuts, callbacks, etc.
|
|
8
|
+
* @returns {number|false} - Number of registered shortcuts or false if no actions
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} FormWatchData
|
|
11
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
12
|
+
* @property {string} context - Current context name
|
|
13
|
+
* @property {string|null} note - Current context note
|
|
14
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
15
|
+
*
|
|
16
|
+
* @typedef {Object} FormDefineData
|
|
17
|
+
* @property {Element} target - The DOM element being watched
|
|
18
|
+
* @property {string} context - Current context name
|
|
19
|
+
* @property {string|null} note - Current context note
|
|
20
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
21
|
+
* @property {Object} viewport - Viewport information with X, Y, width, height
|
|
22
|
+
* @property {Object} sizes - Element dimensions with width, height
|
|
23
|
+
* @property {Object} position - Element position relative to viewport with x, y
|
|
24
|
+
* @property {Object} pagePosition - Element position relative to page with x, y
|
|
25
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
26
|
+
*
|
|
27
|
+
* @typedef {Object} FormActionData
|
|
28
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
29
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
30
|
+
*/
|
|
3
31
|
function _registerShortcutEvents ( dependencies, pluginState ) {
|
|
4
32
|
const
|
|
5
33
|
{ regex, _defaults, ev } = dependencies
|
|
6
34
|
, {
|
|
7
|
-
currentContext : { name: contextName }
|
|
35
|
+
currentContext : { name: contextName, note }
|
|
8
36
|
, shortcuts
|
|
9
37
|
, callbacks
|
|
38
|
+
, ERROR_EVENT_NAME
|
|
39
|
+
, defaultOptions
|
|
10
40
|
} = pluginState
|
|
11
41
|
;
|
|
12
|
-
let watch=[], define=[], action=[];
|
|
42
|
+
let watch=[], define=[], action=[], count = 0;
|
|
13
43
|
if ( contextName == null ) return false
|
|
14
44
|
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => {
|
|
15
|
-
|
|
16
|
-
if ( !isFormEv ) return
|
|
45
|
+
const isFormEv = regex.test ( shortcutName );
|
|
46
|
+
if ( !isFormEv ) return
|
|
47
|
+
if ( shortcutName.includes('SETUP' )) {
|
|
48
|
+
const updateOptions = list.reduce ( ( res, fn ) => {
|
|
49
|
+
const r = fn ({
|
|
50
|
+
dependencies : dependencies.extra,
|
|
51
|
+
defaults : structuredClone ( pluginState.defaultOptions ),
|
|
52
|
+
options : pluginState.listenOptions
|
|
53
|
+
})
|
|
54
|
+
return Object.assign ( res, r )
|
|
55
|
+
}, defaultOptions )
|
|
56
|
+
Object.assign ( pluginState.listenOptions, updateOptions )
|
|
57
|
+
return
|
|
58
|
+
}
|
|
17
59
|
if ( shortcutName === 'FORM:WATCH' ) watch = list
|
|
18
60
|
if ( shortcutName === 'FORM:DEFINE' ) define = list
|
|
19
61
|
if ( shortcutName === 'FORM:ACTION' ) action = list
|
|
62
|
+
|
|
20
63
|
})
|
|
21
64
|
|
|
22
|
-
if ( action.length === 0 ) return
|
|
23
|
-
|
|
24
|
-
|
|
65
|
+
if ( action.length === 0 ) return count
|
|
66
|
+
|
|
67
|
+
const setTypes = new Set ();
|
|
25
68
|
if ( define.length === 0 ) define = [ _defaults.define ]
|
|
26
69
|
if ( watch.length === 0 ) watch = [ _defaults.watch ]
|
|
27
|
-
|
|
70
|
+
const watchList = watch.map ( el => el ({
|
|
71
|
+
dependencies : dependencies.extra
|
|
72
|
+
, context : contextName
|
|
73
|
+
, note
|
|
74
|
+
, options : pluginState.listenOptions
|
|
75
|
+
}) )
|
|
28
76
|
.reduce ( ( res, el) => {
|
|
29
77
|
res.push ( el )
|
|
30
78
|
return res
|
|
31
79
|
}, [])
|
|
32
80
|
pluginState.watchList = document.querySelectorAll ( watchList )
|
|
33
|
-
pluginState.watchList.forEach ( el =>
|
|
81
|
+
pluginState.watchList.forEach ( el => {
|
|
82
|
+
const
|
|
83
|
+
{ left, top, width, height } = el.getBoundingClientRect ()
|
|
84
|
+
, scrollX = window.scrollX
|
|
85
|
+
, scrollY = window.scrollY
|
|
86
|
+
;
|
|
87
|
+
return setTypes.add ( define[0]({
|
|
88
|
+
target : el
|
|
89
|
+
, context : contextName
|
|
90
|
+
, note
|
|
91
|
+
, dependencies : dependencies.extra
|
|
92
|
+
, options : pluginState.listenOptions
|
|
93
|
+
, viewport : {
|
|
94
|
+
X: scrollX
|
|
95
|
+
, Y: scrollY
|
|
96
|
+
, width: window.innerWidth
|
|
97
|
+
, height: window.innerHeight
|
|
98
|
+
}
|
|
99
|
+
, sizes : { width , height }
|
|
100
|
+
, position : { x:left, y:top }
|
|
101
|
+
, pagePosition : { x:left+scrollX, y:top+scrollY }
|
|
102
|
+
})
|
|
103
|
+
) // setTypes
|
|
104
|
+
}) // forEach watchList
|
|
34
105
|
|
|
35
106
|
pluginState.typeFn = define[0] ? define[0] : _defaults.define
|
|
107
|
+
|
|
36
108
|
action.forEach ( act => {
|
|
37
|
-
|
|
38
109
|
if ( !(act instanceof Function)) {
|
|
39
|
-
|
|
110
|
+
ev.emit ( ERROR_EVENT_NAME, `The 'form:action' should be a function.` )
|
|
40
111
|
return false
|
|
41
112
|
}
|
|
42
|
-
|
|
113
|
+
|
|
114
|
+
const list = act ({
|
|
115
|
+
dependencies : dependencies.extra,
|
|
116
|
+
options : pluginState.listenOptions
|
|
117
|
+
})
|
|
118
|
+
|
|
43
119
|
if ( !(list instanceof Array) ) {
|
|
44
|
-
|
|
120
|
+
ev.emit ( ERROR_EVENT_NAME, `Warning: The 'form:action' function should RETURN an array.` )
|
|
45
121
|
return false
|
|
46
122
|
}
|
|
47
|
-
|
|
123
|
+
list.forEach ( ({fn, type, timing, wait=0 }) => {
|
|
48
124
|
if ( setTypes.has ( type) && fn instanceof Function ) {
|
|
49
|
-
|
|
125
|
+
const key = `${type}/${timing}`
|
|
50
126
|
const hasProperty = callbacks.hasOwnProperty ( key );
|
|
51
127
|
hasProperty ?
|
|
52
128
|
callbacks[key].push ( fn ) :
|
|
@@ -60,8 +136,8 @@ const
|
|
|
60
136
|
if ( timing === 'instant' ) pluginState.wait[`${type}`] = wait
|
|
61
137
|
}) // for each act
|
|
62
138
|
}) // for each action
|
|
63
|
-
|
|
64
|
-
|
|
139
|
+
count = Object.keys ( pluginState.callbacks ).length
|
|
140
|
+
return count
|
|
65
141
|
} // _registerShortcutEvents func.
|
|
66
142
|
|
|
67
143
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
|
|
3
5
|
// import all plugin files here
|
|
4
6
|
import _listenDOM from './_listenDOM.js'
|
|
5
7
|
import _normalizeShortcutName from './_normalizeShortcutName.js'
|
|
@@ -11,29 +13,26 @@ import _defaults from './_defaults.js'
|
|
|
11
13
|
/**
|
|
12
14
|
* @function pluginForm
|
|
13
15
|
* @description Plugin for form element shortcuts
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {Object} state - Library state
|
|
16
|
+
* @param {function} setupPlugin - Plugin setup function from the library
|
|
16
17
|
* @param {Object} [options={}] - Plugin options
|
|
17
18
|
* @returns {PluginAPI} Plugin API
|
|
18
19
|
*/
|
|
19
|
-
function pluginForm (
|
|
20
|
+
function pluginForm ( setupPlugin, options={} ) {
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
, deps = {
|
|
25
|
-
ev: dependencies.ev
|
|
26
|
-
, mainDependencies : dependencies
|
|
22
|
+
const
|
|
23
|
+
deps = {
|
|
24
|
+
_defaults
|
|
27
25
|
, regex : /FORM\s*\:/i
|
|
28
|
-
, _defaults
|
|
29
26
|
}
|
|
30
27
|
, pluginState = {
|
|
31
|
-
|
|
32
|
-
,
|
|
33
|
-
,
|
|
34
|
-
,
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
callbacks : {} // Functions callbacks arranged by 'type/timing' : [ callback, ...otherCallbacks ]
|
|
29
|
+
, typeFn : '' // Type definition function
|
|
30
|
+
, watchList : [] // list of watched elements
|
|
31
|
+
, wait : {} // wait time for 'instant' mode. Define by 'type'.
|
|
32
|
+
// Example: { 'input': 200 }, will update from 'input' type every 200ms
|
|
33
|
+
// where 'input' is type received by 'define' function
|
|
34
|
+
, defaultOptions : {}
|
|
35
|
+
, listenOptions : {}
|
|
37
36
|
} // pluginState
|
|
38
37
|
;
|
|
39
38
|
function resetState () {
|
|
@@ -42,37 +41,17 @@ function pluginForm ( dependencies, state, options ) {
|
|
|
42
41
|
pluginState.watchList = []
|
|
43
42
|
pluginState.wait = {}
|
|
44
43
|
} // resetState func.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
getPrefix : ( ) => 'form'
|
|
57
|
-
, shortcutName : key => { // Format a key string according plugin needs
|
|
58
|
-
return _normalizeShortcutName ( key )
|
|
59
|
-
}
|
|
60
|
-
, contextChange : ( ) => {
|
|
61
|
-
resetState ()
|
|
62
|
-
let hasFormShortcuts = _registerShortcutEvents ( deps, pluginState )
|
|
63
|
-
if ( hasFormShortcuts ) formListener.start ()
|
|
64
|
-
else formListener.stop ()
|
|
65
|
-
}
|
|
66
|
-
, mute : () => formListener.stop ()
|
|
67
|
-
, unmute : () => formListener.start ()
|
|
68
|
-
, destroy : () => {
|
|
69
|
-
formListener.stop ()
|
|
70
|
-
// TODO: Clean up state of the plugin
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
}; // pluginAPI
|
|
74
|
-
Object.freeze ( pluginAPI )
|
|
75
|
-
return pluginAPI
|
|
44
|
+
deps.resetState = resetState
|
|
45
|
+
|
|
46
|
+
return setupPlugin ({
|
|
47
|
+
prefix : 'form'
|
|
48
|
+
, _normalizeShortcutName
|
|
49
|
+
, _registerShortcutEvents
|
|
50
|
+
, _listenDOM
|
|
51
|
+
|
|
52
|
+
, pluginState
|
|
53
|
+
, deps
|
|
54
|
+
})
|
|
76
55
|
} // pluginForm func.
|
|
77
56
|
|
|
78
57
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @function _findTarget
|
|
5
|
+
* @description Find the appropriate hover target element by checking if element has any of the target attributes
|
|
6
|
+
* @param {Object} dependencies - Dependencies object
|
|
7
|
+
* @param {Object} state - Plugin state containing listenOptions with hoverTarget array
|
|
8
|
+
* @param {Element} target - DOM element to start searching from
|
|
9
|
+
* @returns {Element|false} - Target element or false if not found
|
|
10
|
+
*/
|
|
11
|
+
function _findTarget ( dependencies, state, target ) {
|
|
12
|
+
const { listenOptions : {hoverTarget}} = state;
|
|
13
|
+
|
|
14
|
+
const t = target;
|
|
15
|
+
if ( t === document.body ) return false
|
|
16
|
+
if ( t === document ) return false
|
|
17
|
+
const found = hoverTarget.some ( attr => ( t.hasAttribute ( attr ) ) )
|
|
18
|
+
if ( found ) return t
|
|
19
|
+
return _findTarget ( dependencies, state, t.parentNode )
|
|
20
|
+
} // _findTarget func.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export default _findTarget
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @function _listenDOM
|
|
4
|
+
* @description Set up DOM event listeners for hover events
|
|
5
|
+
* @param {Object} dependencies - Dependencies object containing ev, _findTarget, resetState, extra
|
|
6
|
+
* @param {Object} state - Plugin state containing listenOptions and currentContext
|
|
7
|
+
* @returns {Object} - Object containing start and stop methods
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Object} HoverEventData
|
|
10
|
+
* @property {Element} target - The DOM element that is being hovered
|
|
11
|
+
* @property {string} context - Current context name
|
|
12
|
+
* @property {string|null} note - Current context note
|
|
13
|
+
* @property {Event} event - The original DOM event
|
|
14
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
15
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
16
|
+
* @property {Object} viewport - Viewport information with X, Y, width, height
|
|
17
|
+
* @property {Object} sizes - Element dimensions with width, height
|
|
18
|
+
* @property {Object} position - Element position relative to viewport with x, y
|
|
19
|
+
* @property {Object} pagePosition - Element position relative to page with x, y
|
|
20
|
+
* @property {string} type - Event type ('hover')
|
|
21
|
+
*/
|
|
22
|
+
function _listenDOM ( dependencies, state ) {
|
|
23
|
+
const {
|
|
24
|
+
ev
|
|
25
|
+
, _findTarget
|
|
26
|
+
, resetState
|
|
27
|
+
, extra
|
|
28
|
+
} = dependencies;
|
|
29
|
+
|
|
30
|
+
function inside ( rect, x, y ) {
|
|
31
|
+
if ( !rect ) return false;
|
|
32
|
+
return (
|
|
33
|
+
x >= rect.left &&
|
|
34
|
+
x <= rect.right &&
|
|
35
|
+
y >= rect.top &&
|
|
36
|
+
y <= rect.bottom
|
|
37
|
+
);
|
|
38
|
+
} // inside func.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function listenForHover ( event ) {
|
|
43
|
+
const
|
|
44
|
+
x = event.clientX
|
|
45
|
+
, y = event.clientY
|
|
46
|
+
;
|
|
47
|
+
|
|
48
|
+
const
|
|
49
|
+
{
|
|
50
|
+
hovered
|
|
51
|
+
, hoverRectangle
|
|
52
|
+
, listenOptions
|
|
53
|
+
, hoverTimer
|
|
54
|
+
, leaveTimer
|
|
55
|
+
, lastEvent
|
|
56
|
+
, lastHoverTarget
|
|
57
|
+
} = state
|
|
58
|
+
, target = _findTarget ( dependencies, state, event.target )
|
|
59
|
+
;
|
|
60
|
+
|
|
61
|
+
if ( inside ( hoverRectangle, x, y ) ) return
|
|
62
|
+
|
|
63
|
+
function getData (tg) {
|
|
64
|
+
const
|
|
65
|
+
{ left, top, width, height } = tg.getBoundingClientRect ()
|
|
66
|
+
, scrollX = window.scrollX
|
|
67
|
+
, scrollY = window.scrollY
|
|
68
|
+
;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
target : tg
|
|
72
|
+
, context: state.currentContext.name
|
|
73
|
+
, note : state.currentContext.note
|
|
74
|
+
, event
|
|
75
|
+
, dependencies : extra
|
|
76
|
+
, options : state.listenOptions
|
|
77
|
+
, viewport : { // Viewport scroll positions
|
|
78
|
+
X:scrollX
|
|
79
|
+
, Y:scrollY
|
|
80
|
+
, width : window.innerWidth
|
|
81
|
+
, height : window.innerHeight
|
|
82
|
+
}
|
|
83
|
+
, sizes : { width, height } // Element sizes
|
|
84
|
+
, position : { x:left, y:top } // Position relative to viewport
|
|
85
|
+
, pagePosition : { x:left+scrollX, y:top+scrollY } // Position relative to page
|
|
86
|
+
, type: 'hover'
|
|
87
|
+
}
|
|
88
|
+
} // getData func.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if ( target !== hovered ) { // When target is different from hovered
|
|
93
|
+
if ( hovered && !target ) { // We have 'hovered' but no new target
|
|
94
|
+
state.hovered = false
|
|
95
|
+
state.hoverRectangle = null
|
|
96
|
+
|
|
97
|
+
if ( hoverTimer ) {
|
|
98
|
+
clearTimeout ( hoverTimer )
|
|
99
|
+
state.hoverTimer = null
|
|
100
|
+
}
|
|
101
|
+
if ( lastEvent === 'off' ) return
|
|
102
|
+
state.leaveTimer = setTimeout ( () => {
|
|
103
|
+
ev.emit ( 'HOVER:OFF', getData(hovered) )
|
|
104
|
+
state.leaveTimer = null
|
|
105
|
+
state.lastEvent = 'off'
|
|
106
|
+
}, listenOptions.wait )
|
|
107
|
+
return
|
|
108
|
+
} // if hovered
|
|
109
|
+
|
|
110
|
+
if ( hovered ) { // We have 'hovered' and new target
|
|
111
|
+
// Close immediately the previous hover
|
|
112
|
+
ev.emit ( 'HOVER:OFF', getData(hovered) )
|
|
113
|
+
state.leaveTimer = null
|
|
114
|
+
state.lastEvent = 'off'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
clearTimeout ( leaveTimer )
|
|
118
|
+
clearTimeout ( hoverTimer )
|
|
119
|
+
|
|
120
|
+
state.hovered = target
|
|
121
|
+
state.hoverRectangle = target.getBoundingClientRect ()
|
|
122
|
+
state.hoverTimer = setTimeout ( () => {
|
|
123
|
+
ev.emit ( 'HOVER:ON', getData(target))
|
|
124
|
+
state.hoverTimer = null
|
|
125
|
+
state.lastHoverTarget = target
|
|
126
|
+
state.lastEvent = 'on'
|
|
127
|
+
}, listenOptions.wait )
|
|
128
|
+
|
|
129
|
+
} // if target !== hovered
|
|
130
|
+
} // listenForHover func.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
function start () {
|
|
134
|
+
if ( state.active ) return
|
|
135
|
+
document.addEventListener ( 'mousemove' , listenForHover )
|
|
136
|
+
state.active = true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
function stop () {
|
|
141
|
+
if ( !state.active ) return
|
|
142
|
+
document.removeEventListener ( 'mousemove' , listenForHover )
|
|
143
|
+
resetState ()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { start, stop }
|
|
147
|
+
|
|
148
|
+
} // _listenDOM func.
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
export default _listenDOM
|
|
153
|
+
|
|
154
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function _normalizeShortcutName ( name ) {
|
|
4
|
+
const
|
|
5
|
+
upperCase = name.toUpperCase ()
|
|
6
|
+
, regex = /HOVER\s*\:/i
|
|
7
|
+
, isHoverShortcut = regex.test ( upperCase )
|
|
8
|
+
;
|
|
9
|
+
|
|
10
|
+
const sliceIndex = upperCase.indexOf ( ':' );
|
|
11
|
+
|
|
12
|
+
if ( !isHoverShortcut ) return name
|
|
13
|
+
const shortcut = upperCase.slice(sliceIndex+1).trim ()
|
|
14
|
+
return `HOVER:${shortcut}`
|
|
15
|
+
} // _normalizeShortcutName func.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export default _normalizeShortcutName
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function _registerShortcutEvents
|
|
3
|
+
* @description Register hover shortcut events and handle setup
|
|
4
|
+
* @param {Object} dependencies - Dependencies object containing regex, _defaults, ev
|
|
5
|
+
* @param {Object} pluginState - Plugin state containing currentContext, shortcuts, ERROR_EVENT_NAME
|
|
6
|
+
* @returns {number} - Number of registered shortcuts
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} HoverSetupData
|
|
9
|
+
* @property {Object} dependencies - Extra dependencies object
|
|
10
|
+
* @property {Object} defaults - Default options (clone of pluginState.defaultOptions)
|
|
11
|
+
* @property {Object} options - Plugin state listenOptions (reference to pluginState.listenOptions)
|
|
12
|
+
*/
|
|
13
|
+
function _registerShortcutEvents ( dependencies, pluginState ) {
|
|
14
|
+
let count = 0;
|
|
15
|
+
let hasSetup = false
|
|
16
|
+
const df = pluginState.defaultOptions;
|
|
17
|
+
const
|
|
18
|
+
{ regex, _defaults, ev } = dependencies
|
|
19
|
+
, {
|
|
20
|
+
currentContext : { name: contextName }
|
|
21
|
+
, shortcuts
|
|
22
|
+
, ERROR_EVENT_NAME
|
|
23
|
+
} = pluginState
|
|
24
|
+
;
|
|
25
|
+
if ( contextName == null ) return count // No context
|
|
26
|
+
Object.entries ( shortcuts[contextName] ).forEach ( ([shortcutName, list ]) => {
|
|
27
|
+
const isHoverEv = regex.test ( shortcutName );
|
|
28
|
+
if ( !isHoverEv ) return
|
|
29
|
+
if ( shortcutName === 'HOVER:SETUP' ) {
|
|
30
|
+
hasSetup = true
|
|
31
|
+
const updateOptions = list.reduce ( ( res, fn ) => {
|
|
32
|
+
const r = fn ({
|
|
33
|
+
dependencies : dependencies.extra,
|
|
34
|
+
defaults : structuredClone ( pluginState.defaultOptions ),
|
|
35
|
+
options : pluginState.listenOptions
|
|
36
|
+
})
|
|
37
|
+
return Object.assign ( res, r )
|
|
38
|
+
}, df )
|
|
39
|
+
Object.assign ( pluginState.listenOptions, updateOptions )
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
count++
|
|
43
|
+
})
|
|
44
|
+
if ( !hasSetup ) Object.assign ( pluginState.listenOptions, df )
|
|
45
|
+
return count
|
|
46
|
+
} // _registerShortcutEvents func.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export default _registerShortcutEvents
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import _findTarget from "./_findTarget"
|
|
4
|
+
import _listenDOM from "./_listenDOM"
|
|
5
|
+
import _normalizeShortcutName from "./_normalizeShortcutName"
|
|
6
|
+
import _registerShortcutEvents from "./_registerShortcutEvents"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @function pluginHover
|
|
12
|
+
* @description Plugin for mouse hover shortcuts
|
|
13
|
+
* @param {function} setupPlugin - Plugin setup function from the library
|
|
14
|
+
* @param {Object} [options={}] - Plugin options
|
|
15
|
+
* @param {string[]} [options.hoverTarget=['data-hover']] - Array of attribute names for hover targets
|
|
16
|
+
* @param {number} [options.wait=320] - Time to wait for hover sequence in ms
|
|
17
|
+
* @returns {PluginAPI} Plugin API
|
|
18
|
+
*/
|
|
19
|
+
function pluginHover ( setupPlugin, options={} ) {
|
|
20
|
+
const
|
|
21
|
+
deps = {
|
|
22
|
+
_findTarget
|
|
23
|
+
, regex : /HOVER\s*\:/i
|
|
24
|
+
}
|
|
25
|
+
, pluginState = {
|
|
26
|
+
// currentContext // { name, note } of the current context
|
|
27
|
+
active : false // Plugin activity state
|
|
28
|
+
, hovered : false // False or the hovered HTML element
|
|
29
|
+
, hoverRectangle : null // Hovered HTML element rectangle
|
|
30
|
+
, hoverTimer : null // Timeout for reducing hover on events
|
|
31
|
+
, leaveTimer : null // Timeout for reducing hover off events
|
|
32
|
+
, lastEvent : '' // 'on' or 'off'. Last executed hover event
|
|
33
|
+
, lastHoverTarget : null // Last hovered HTML element or null
|
|
34
|
+
, defaultOptions : {
|
|
35
|
+
hoverTarget : [ 'data-hover' ],
|
|
36
|
+
wait : 320 // 320 ms
|
|
37
|
+
}
|
|
38
|
+
, listenOptions : {
|
|
39
|
+
hoverTarget : [ 'data-hover' ],
|
|
40
|
+
wait : 320 // 320 ms
|
|
41
|
+
}
|
|
42
|
+
} // pluginState
|
|
43
|
+
;
|
|
44
|
+
|
|
45
|
+
function resetState () {
|
|
46
|
+
pluginState.active = false
|
|
47
|
+
pluginState.hovered = false
|
|
48
|
+
pluginState.hoverRectangle = null
|
|
49
|
+
clearTimeout ( pluginState.hoverTimer )
|
|
50
|
+
clearTimeout ( pluginState.leaveTimer )
|
|
51
|
+
pluginState.hoverTimer = null
|
|
52
|
+
pluginState.leaveTimer = null
|
|
53
|
+
pluginState.lastHoverTarget = null
|
|
54
|
+
} // resetState func.
|
|
55
|
+
deps.resetState = resetState
|
|
56
|
+
|
|
57
|
+
return setupPlugin ({
|
|
58
|
+
prefix : 'hover'
|
|
59
|
+
, _normalizeShortcutName
|
|
60
|
+
, _registerShortcutEvents
|
|
61
|
+
, _listenDOM
|
|
62
|
+
, pluginState
|
|
63
|
+
, deps
|
|
64
|
+
})
|
|
65
|
+
} // pluginHover func.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export default pluginHover
|
|
70
|
+
|
|
71
|
+
|