@peter.naydenov/shortcuts 3.5.2 → 4.0.1

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 (135) 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 +32 -2
  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 -31
  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 -29
  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 -29
  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/dist/src/main.d.ts +172 -0
  44. package/dist/src/methods/_normalizeWithPlugins.d.ts +64 -0
  45. package/dist/src/methods/_readShortcutWithPlugins.d.ts +9 -0
  46. package/dist/src/methods/_setupPlugin.d.ts +9 -0
  47. package/dist/src/methods/_systemAction.d.ts +9 -0
  48. package/dist/src/methods/changeContext.d.ts +9 -0
  49. package/dist/src/methods/index.d.ts +19 -0
  50. package/dist/src/methods/listShortcuts.d.ts +2 -0
  51. package/dist/src/methods/load.d.ts +9 -0
  52. package/dist/src/methods/unload.d.ts +9 -0
  53. package/dist/src/plugins/click/_findTarget.d.ts +10 -0
  54. package/dist/src/plugins/click/_listenDOM.d.ts +78 -0
  55. package/dist/src/plugins/click/_normalizeShortcutName.d.ts +8 -0
  56. package/dist/src/plugins/click/_readClickEvent.d.ts +2 -0
  57. package/dist/src/plugins/click/_registerShortcutEvents.d.ts +28 -0
  58. package/dist/src/plugins/click/index.d.ts +16 -0
  59. package/dist/src/plugins/form/_defaults.d.ts +17 -0
  60. package/dist/src/plugins/form/_listenDOM.d.ts +68 -0
  61. package/dist/src/plugins/form/_normalizeShortcutName.d.ts +2 -0
  62. package/dist/src/plugins/form/_registerShortcutEvents.d.ts +96 -0
  63. package/dist/src/plugins/form/index.d.ts +9 -0
  64. package/dist/src/plugins/hover/_findTarget.d.ts +10 -0
  65. package/dist/src/plugins/hover/_listenDOM.d.ts +68 -0
  66. package/dist/src/plugins/hover/_normalizeShortcutName.d.ts +2 -0
  67. package/dist/src/plugins/hover/_registerShortcutEvents.d.ts +28 -0
  68. package/dist/src/plugins/hover/index.d.ts +14 -0
  69. package/dist/src/plugins/key/_listenDOM.d.ts +63 -0
  70. package/dist/src/plugins/key/_normalizeShortcutName.d.ts +2 -0
  71. package/dist/src/plugins/key/_readKeyEvent.d.ts +2 -0
  72. package/dist/src/plugins/key/_registerShortcutEvents.d.ts +28 -0
  73. package/dist/src/plugins/key/_specialChars.d.ts +7 -0
  74. package/dist/src/plugins/key/index.d.ts +14 -0
  75. package/dist/src/plugins/scroll/_listenDOM.d.ts +58 -0
  76. package/dist/src/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
  77. package/dist/src/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
  78. package/dist/src/plugins/scroll/index.d.ts +16 -0
  79. package/eslint.config.js +80 -0
  80. package/html/assets/index-COTh6lXR.css +1 -0
  81. package/html/assets/index-DOkKC3NI.js +53 -0
  82. package/html/bg.png +0 -0
  83. package/html/favicon.ico +0 -0
  84. package/html/favicon.svg +5 -0
  85. package/html/html.meta.json.gz +0 -0
  86. package/html/index.html +32 -0
  87. package/package.json +24 -19
  88. package/shortcuts.png +0 -0
  89. package/src/main.js +52 -22
  90. package/src/methods/_normalizeWithPlugins.js +26 -2
  91. package/src/methods/_readShortcutWithPlugins.js +9 -2
  92. package/src/methods/_setupPlugin.js +93 -0
  93. package/src/methods/_systemAction.js +12 -4
  94. package/src/methods/changeContext.js +11 -3
  95. package/src/methods/index.js +2 -0
  96. package/src/methods/listShortcuts.js +5 -12
  97. package/src/methods/load.js +11 -4
  98. package/src/methods/unload.js +8 -1
  99. package/src/plugins/click/_findTarget.js +11 -5
  100. package/src/plugins/click/_listenDOM.js +58 -20
  101. package/src/plugins/click/_normalizeShortcutName.js +11 -4
  102. package/src/plugins/click/_readClickEvent.js +1 -1
  103. package/src/plugins/click/_registerShortcutEvents.js +33 -5
  104. package/src/plugins/click/index.js +33 -60
  105. package/src/plugins/form/_defaults.js +13 -3
  106. package/src/plugins/form/_listenDOM.js +46 -9
  107. package/src/plugins/form/_normalizeShortcutName.js +2 -2
  108. package/src/plugins/form/_registerShortcutEvents.js +93 -17
  109. package/src/plugins/form/index.js +25 -56
  110. package/src/plugins/hover/_findTarget.js +26 -0
  111. package/src/plugins/hover/_listenDOM.js +154 -0
  112. package/src/plugins/hover/_normalizeShortcutName.js +21 -0
  113. package/src/plugins/hover/_registerShortcutEvents.js +51 -0
  114. package/src/plugins/hover/index.js +71 -0
  115. package/src/plugins/key/_listenDOM.js +67 -33
  116. package/src/plugins/key/_normalizeShortcutName.js +4 -3
  117. package/src/plugins/key/_readKeyEvent.js +1 -1
  118. package/src/plugins/key/_registerShortcutEvents.js +34 -5
  119. package/src/plugins/key/_specialChars.js +5 -0
  120. package/src/plugins/key/index.js +34 -59
  121. package/src/plugins/scroll/_listenDOM.js +141 -0
  122. package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
  123. package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
  124. package/src/plugins/scroll/index.js +61 -0
  125. package/test/01-general.test.js +92 -23
  126. package/test/02-key.test.js +241 -40
  127. package/test/03-click.test.js +291 -47
  128. package/test/04-form.test.js +241 -47
  129. package/test/05-hover.test.js +463 -0
  130. package/test/06-scroll.test.js +374 -0
  131. package/test-helpers/Block.jsx +3 -2
  132. package/test-helpers/style.css +6 -1
  133. package/tsconfig.json +2 -1
  134. package/vitest.config.js +13 -11
  135. 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
- , mainDependencies
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 : mainDependencies.extra
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 = listenOptions.maxClicks; // Maximum number of clicks per target
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 = listenOptions.maxClicks; // Maximum number of clicks per target
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
- // Clear any pending timers to prevent state pollution between tests
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 ) return name
21
- let shortcutArray = upperCase.slice(sliceIndex+1).trim().split('-').map ( x => x.trim() );
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,7 @@
1
1
  'use strict'
2
2
 
3
3
  function _readClickEvent ( event, count ) {
4
- let
4
+ const
5
5
  { shiftKey, altKey, ctrlKey, key, button } = event
6
6
  , mouseNames = [ 'LEFT', 'MIDDLE', 'RIGHT' ]
7
7
  , mouseEvent = `CLICK:${mouseNames[button]}-${count}`
@@ -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 0
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
- let isClickEv = regex.test ( shortcutName );
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
- let [ ,numberClicks ] = shortcutName.slice(6).split('-');
21
- if ( listenOptions.maxClicks < numberClicks ) listenOptions.maxClicks = numberClicks
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,14 +1,6 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * @typedef {Object} PluginAPI
5
- * @property {function(): string} getPrefix - Get plugin prefix
6
- * @property {function(string): string} shortcutName - Format shortcut name
7
- * @property {function(string): void} contextChange - Handle context change
8
- * @property {function(): void} mute - Mute the plugin
9
- * @property {function(): void} unmute - Unmute the plugin
10
- * @property {function(): void} destroy - Destroy the plugin
11
- */
3
+
12
4
 
13
5
  import _findTarget from "./_findTarget"
14
6
  import _listenDOM from "./_listenDOM"
@@ -22,69 +14,50 @@ import _registerShortcutEvents from "./_registerShortcutEvents"
22
14
  /**
23
15
  * @function pluginClick
24
16
  * @description Plugin for mouse click shortcuts
25
- * @param {Object} dependencies - Internal dependencies
26
- * @param {Object} state - Library state
17
+ * @param {function} setupPlugin - Plugin setup function from the library
27
18
  * @param {Object} [options={}] - Plugin options
28
19
  * @param {number} [options.mouseWait=320] - Time to wait for click sequence in ms
29
- * @param {string} [options.clickTarget='click'] - Data attribute name for click targets
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
30
22
  * @returns {PluginAPI} Plugin API
31
23
  */
32
- function pluginClick ( dependencies, state, options ) {
33
- let
34
- { currentContext, shortcuts } = state
35
- , { inAPI } = dependencies
36
- , deps = {
37
- ev: dependencies.ev
38
- , _findTarget
24
+ function pluginClick ( setupPlugin, options = {}) {
25
+ const
26
+ deps = {
27
+ _findTarget
39
28
  , _readClickEvent
40
- , mainDependencies : dependencies
41
29
  , regex : /CLICK\s*\:/i
42
30
  }
43
31
  , pluginState = {
44
- currentContext
45
- , active : false
46
- , shortcuts
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
+ }
47
39
  , listenOptions : {
48
- mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
49
- , maxClicks : 1 // How many clicks can be pressed in a sequence. Controlled automatically by '_registerShortcutEvents' function.
50
- , 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
51
42
  }
43
+ , streamKeys : (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false // Keyboard stream function
52
44
  } // pluginState
53
- ;
45
+ ;
54
46
 
55
- // Read shortcuts names from all context entities and normalize entries related to the plugin
56
- inAPI._normalizeWithPlugins ( _normalizeShortcutName )
57
-
58
- let
59
- mouseListener = _listenDOM ( deps, pluginState )
60
- , countShortcuts = _registerShortcutEvents ( deps, pluginState )
61
- ;
62
-
63
- if ( countShortcuts > 0 ) mouseListener.start ()
64
-
65
- let pluginAPI = {
66
- getPrefix : () => 'click'
67
- , shortcutName : key => { // Format a key string according plugin needs
68
- return _normalizeShortcutName ( key )
69
- }
70
- , contextChange : () => {
71
- countShortcuts = _registerShortcutEvents ( deps, pluginState )
72
- if ( countShortcuts < 1 ) { // Remove DOM listener if there are no shortcuts in the current context
73
- mouseListener.stop ()
74
- }
75
- if ( countShortcuts > 0 ) { // Add DOM listener if there are shortcuts in the current context
76
- mouseListener.start ()
77
- }
78
- }
79
- , mute : () => {
80
-
81
- mouseListener.stop ()
82
- }
83
- , unmute : () => mouseListener.start ()
84
- , destroy : () => mouseListener.stop ()
85
- }; // pluginAPI
86
- Object.freeze ( pluginAPI )
87
- 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
+ })
88
61
  } // pluginClick func.
89
62
 
90
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: (el) => {
4
- if ( el.type === 'checkbox' || el.type === 'radio' ) {
13
+ , define: ({ target }) => {
14
+ if ( target.type === 'checkbox' || target.type === 'radio' ) {
5
15
  return 'checkbox'
6
16
  }
7
- if ( el.type == 'button' || el.type=='submit' ) {
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.mainDependencies.extra
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
- , type = typeFn ( target )
26
- , prop = setupData ( dependencies, state, event, type )
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
- , target = event.target
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
- , target = event.target
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
- let fn = () => ev.emit ( key, prop, callbacks[key] )
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
- let shortcut = upperCase.slice(sliceIndex+1).trim ()
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