@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.
Files changed (98) hide show
  1. package/API.md +939 -0
  2. package/CODE_OF_CONDUCT.md +84 -0
  3. package/CONTRIBUTING.md +476 -0
  4. package/Changelog.md +30 -1
  5. package/How.to.create.plugins.md +929 -0
  6. package/Migration.guide.md +48 -0
  7. package/README.md +396 -24
  8. package/dist/main.d.ts +54 -2
  9. package/dist/methods/_normalizeWithPlugins.d.ts +63 -1
  10. package/dist/methods/_readShortcutWithPlugins.d.ts +8 -1
  11. package/dist/methods/_setupPlugin.d.ts +9 -0
  12. package/dist/methods/_systemAction.d.ts +8 -1
  13. package/dist/methods/changeContext.d.ts +8 -1
  14. package/dist/methods/index.d.ts +2 -0
  15. package/dist/methods/listShortcuts.d.ts +1 -16
  16. package/dist/methods/load.d.ts +8 -1
  17. package/dist/methods/unload.d.ts +8 -1
  18. package/dist/plugins/click/_findTarget.d.ts +9 -1
  19. package/dist/plugins/click/_listenDOM.d.ts +76 -3
  20. package/dist/plugins/click/_normalizeShortcutName.d.ts +7 -1
  21. package/dist/plugins/click/_registerShortcutEvents.d.ts +26 -0
  22. package/dist/plugins/click/index.d.ts +6 -5
  23. package/dist/plugins/form/_defaults.d.ts +13 -1
  24. package/dist/plugins/form/_listenDOM.d.ts +66 -3
  25. package/dist/plugins/form/_registerShortcutEvents.d.ts +95 -1
  26. package/dist/plugins/form/index.d.ts +2 -3
  27. package/dist/plugins/hover/_findTarget.d.ts +10 -0
  28. package/dist/plugins/hover/_listenDOM.d.ts +68 -0
  29. package/dist/plugins/hover/_normalizeShortcutName.d.ts +2 -0
  30. package/dist/plugins/hover/_registerShortcutEvents.d.ts +28 -0
  31. package/dist/plugins/hover/index.d.ts +14 -0
  32. package/dist/plugins/key/_listenDOM.d.ts +61 -3
  33. package/dist/plugins/key/_registerShortcutEvents.d.ts +26 -0
  34. package/dist/plugins/key/_specialChars.d.ts +6 -31
  35. package/dist/plugins/key/index.d.ts +2 -3
  36. package/dist/plugins/scroll/_listenDOM.d.ts +58 -0
  37. package/dist/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
  38. package/dist/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
  39. package/dist/plugins/scroll/index.d.ts +16 -0
  40. package/dist/shortcuts.cjs +1 -1
  41. package/dist/shortcuts.esm.mjs +1 -1
  42. package/dist/shortcuts.umd.js +1 -1
  43. package/eslint.config.js +80 -0
  44. package/html/assets/index-COTh6lXR.css +1 -0
  45. package/html/assets/index-DOkKC3NI.js +53 -0
  46. package/html/bg.png +0 -0
  47. package/html/favicon.ico +0 -0
  48. package/html/favicon.svg +5 -0
  49. package/html/html.meta.json.gz +0 -0
  50. package/html/index.html +32 -0
  51. package/package.json +16 -12
  52. package/shortcuts.png +0 -0
  53. package/src/main.js +52 -22
  54. package/src/methods/_normalizeWithPlugins.js +26 -2
  55. package/src/methods/_readShortcutWithPlugins.js +9 -2
  56. package/src/methods/_setupPlugin.js +93 -0
  57. package/src/methods/_systemAction.js +12 -4
  58. package/src/methods/changeContext.js +11 -3
  59. package/src/methods/index.js +2 -0
  60. package/src/methods/listShortcuts.js +5 -12
  61. package/src/methods/load.js +11 -4
  62. package/src/methods/unload.js +8 -1
  63. package/src/plugins/click/_findTarget.js +11 -5
  64. package/src/plugins/click/_listenDOM.js +58 -20
  65. package/src/plugins/click/_normalizeShortcutName.js +11 -4
  66. package/src/plugins/click/_readClickEvent.js +1 -1
  67. package/src/plugins/click/_registerShortcutEvents.js +33 -5
  68. package/src/plugins/click/index.js +34 -51
  69. package/src/plugins/form/_defaults.js +13 -3
  70. package/src/plugins/form/_listenDOM.js +46 -9
  71. package/src/plugins/form/_normalizeShortcutName.js +2 -2
  72. package/src/plugins/form/_registerShortcutEvents.js +93 -17
  73. package/src/plugins/form/index.js +26 -47
  74. package/src/plugins/hover/_findTarget.js +26 -0
  75. package/src/plugins/hover/_listenDOM.js +154 -0
  76. package/src/plugins/hover/_normalizeShortcutName.js +21 -0
  77. package/src/plugins/hover/_registerShortcutEvents.js +51 -0
  78. package/src/plugins/hover/index.js +71 -0
  79. package/src/plugins/key/_listenDOM.js +67 -33
  80. package/src/plugins/key/_normalizeShortcutName.js +4 -3
  81. package/src/plugins/key/_readKeyEvent.js +1 -1
  82. package/src/plugins/key/_registerShortcutEvents.js +34 -5
  83. package/src/plugins/key/_specialChars.js +5 -0
  84. package/src/plugins/key/index.js +35 -50
  85. package/src/plugins/scroll/_listenDOM.js +141 -0
  86. package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
  87. package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
  88. package/src/plugins/scroll/index.js +61 -0
  89. package/test/01-general.test.js +92 -23
  90. package/test/02-key.test.js +241 -40
  91. package/test/03-click.test.js +291 -47
  92. package/test/04-form.test.js +241 -47
  93. package/test/05-hover.test.js +463 -0
  94. package/test/06-scroll.test.js +374 -0
  95. package/test-helpers/Block.jsx +3 -2
  96. package/test-helpers/style.css +6 -1
  97. package/vitest.config.js +13 -11
  98. 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
- let isFormEv = regex.test ( shortcutName );
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 false
23
-
24
- let setTypes = new Set ();
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
- let watchList = watch.map ( el => el() )
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 => setTypes.add (define[0](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
- console.warn ( `Warning: The 'form:action' should be a function.` )
110
+ ev.emit ( ERROR_EVENT_NAME, `The 'form:action' should be a function.` )
40
111
  return false
41
112
  }
42
- let list = act ()
113
+
114
+ const list = act ({
115
+ dependencies : dependencies.extra,
116
+ options : pluginState.listenOptions
117
+ })
118
+
43
119
  if ( !(list instanceof Array) ) {
44
- console.warn ( `Warning: The 'form:action' function should RETURN an array.` )
120
+ ev.emit ( ERROR_EVENT_NAME, `Warning: The 'form:action' function should RETURN an array.` )
45
121
  return false
46
122
  }
47
- act().forEach ( ({fn, type, timing, wait=0 }) => {
123
+ list.forEach ( ({fn, type, timing, wait=0 }) => {
48
124
  if ( setTypes.has ( type) && fn instanceof Function ) {
49
- let key = `${type}/${timing}`
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
- if ( Object.keys(pluginState.callbacks).length > 0 ) return true
64
- else return false
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 {Object} dependencies - Internal dependencies
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 ( dependencies, state, options ) {
20
+ function pluginForm ( setupPlugin, options={} ) {
20
21
 
21
- let
22
- { currentContext, shortcuts } = state
23
- , { inAPI } = dependencies
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
- currentContext
32
- , shortcuts
33
- , callbacks : {} // Functions callbacks arranged by 'type/timing' : [ callback, ...otherCallbacks ]
34
- , typeFn : '' // Type definition function
35
- , watchList : [] // list of watched elements
36
- , wait : {} // wait time for 'instant' mode
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
- // Read shortcuts names from all context entities and normalize entries related to the plugin
47
- inAPI._normalizeWithPlugins ( _normalizeShortcutName )
48
- let formListener = _listenDOM ( deps, pluginState );
49
-
50
- if ( currentContext.name ) {
51
- let hasFormShortcuts = _registerShortcutEvents ( deps, pluginState )
52
- if ( hasFormShortcuts ) formListener.start ()
53
- }
54
-
55
- let pluginAPI = {
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
+