@madj2k/fe-frontend-kit 2.0.37 → 2.0.38

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.
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @author Steffen Kroggel <developer@steffenkroggel.de>
9
9
  * @copyright 2025 Steffen Kroggel
10
- * @version 2.0.2
10
+ * @version 2.0.3
11
11
  * @license GNU General Public License v3.0
12
12
  * @see https://www.gnu.org/licenses/gpl-3.0.en.html
13
13
  *
@@ -50,10 +50,9 @@ class Madj2kFlyoutMenu {
50
50
  menuContainerClass: "js-flyout-container",
51
51
  menuInnerClass: "js-flyout-inner",
52
52
  heightCalculationClass: 'calculate',
53
+ hoverParentClass: 'nav-main',
53
54
  heightMode: 'full',
54
55
  eventMode: 'click',
55
- eventModeOpen: '',
56
- eventModeClose: '',
57
56
  paddingBehavior: 0,
58
57
  paddingViewPortMinWidth: 0,
59
58
  animationDuration: 500,
@@ -77,6 +76,8 @@ class Madj2kFlyoutMenu {
77
76
  this.settings.$menuContainer = this.settings.$menu.querySelector(`.${this.settings.menuContainerClass}`);
78
77
  this.settings.$menuInner = this.settings.$menu.querySelector(`.${this.settings.menuInnerClass}`);
79
78
 
79
+ // Bind persistent event handlers
80
+
80
81
  this.initNoScrollHelper();
81
82
  this.resizeAndPositionMenu();
82
83
  this.paddingMenu();
@@ -115,40 +116,102 @@ class Madj2kFlyoutMenu {
115
116
  }
116
117
  }
117
118
 
119
+
120
+ /**
121
+ * Handles mouseleave on menu container
122
+ * @param {MouseEvent} e
123
+ */
124
+ containerLeaveEvent(e) {
125
+ const to = e.relatedTarget;
126
+ const parentContainer = this.$element.closest(this.settings.hoverParentSelector) || null;
127
+ if (to && this.$element.contains(to)) return;
128
+ if (to && parentContainer?.contains(to)) return;
129
+ if (this.$element.classList.contains(this.settings.openStatusClass)) {
130
+ document.dispatchEvent(new CustomEvent('madj2k-flyoutmenu-close'));
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Handles mouseleave on trigger element
136
+ * @param {MouseEvent} e
137
+ */
138
+ triggerLeaveEvent(e) {
139
+ const to = e.relatedTarget;
140
+ const parentContainer = this.$element.closest('.' + this.settings.hoverParentClass) || null;
141
+
142
+ if (to && this.settings.$menu.contains(to)) return;
143
+ if (to && parentContainer?.contains(to)) return;
144
+ if (this.$element.classList.contains(this.settings.openStatusClass)) {
145
+ document.dispatchEvent(new CustomEvent('madj2k-flyoutmenu-close'));
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Handles touchstart outside the flyout menu
151
+ * @param {TouchEvent} e
152
+ */
153
+ touchStartEvent(e) {
154
+ const target = e.target;
155
+ if (!target) return;
156
+ if (this.settings.$menuInner.contains(target)) return;
157
+ if (this.$element.classList.contains(this.settings.openStatusClass)) {
158
+ document.dispatchEvent(new CustomEvent('madj2k-flyoutmenu-close'));
159
+ }
160
+ }
161
+
118
162
  /**
119
163
  * Binds all necessary event listeners
120
164
  */
121
165
  bindEvents() {
122
166
  if (this.settings.$closeBtn) {
123
-
124
- if (this.settings.eventModeClose) {
125
- this.settings.$closeBtn.addEventListener(this.settings.eventModeClose, e => this.closeEvent(e));
126
- } else {
127
- this.settings.$closeBtn.addEventListener(this.settings.eventMode, e => this.closeEvent(e));
128
- }
167
+ const closeEvent = this.settings.eventMode === 'mouseover' ? 'mouseenter' : 'click';
168
+ this.settings.$closeBtn.addEventListener(closeEvent, e => this.closeEvent(e));
129
169
  this.settings.$closeBtn.addEventListener('keydown', e => this.keyboardEvent(e));
130
170
  }
131
171
 
132
- if (this.settings.eventModeOpen || this.settings.eventModeClose) {
133
- if (this.settings.eventModeOpen) {
134
- this.$element.addEventListener(this.settings.eventModeOpen, e => this.openEvent(e));
135
- }
136
- if (this.settings.eventModeClose) {
137
- this.$element.addEventListener(this.settings.eventModeClose, e => this.closeEvent(e));
138
- }
172
+ if (this.settings.eventMode === 'mouseover') {
173
+ this.$element.addEventListener('mouseenter', e => this.openEvent(e));
174
+ this.$element.addEventListener('mouseleave',e => this.triggerLeaveEvent(e));
175
+ this.settings.$menuContainer?.addEventListener('mouseleave', e => this.containerLeaveEvent(e));
176
+ document.addEventListener('touchstart', e => this.touchStartEvent(e), { passive: true });
139
177
  } else {
140
- this.$element.addEventListener(this.settings.eventMode, e => this.toggleEvent(e));
178
+ this.$element.addEventListener('click', e => this.toggleEvent(e));
141
179
  }
142
180
 
143
181
  this.$element.addEventListener('keydown', e => this.keyboardEvent(e));
144
-
145
182
  this.settings.$menu.querySelectorAll('a,button,input,textarea,select')
146
183
  .forEach(el => el.addEventListener('keydown', e => this.keyboardEvent(e)));
147
-
148
184
  document.addEventListener('madj2k-flyoutmenu-close', e => this.closeEvent(e));
149
185
  document.addEventListener('madj2k-flyoutmenu-resize', e => this.resizeAndPositionMenuEvent(e));
150
186
  }
151
187
 
188
+
189
+ /**
190
+ * Removes all event listeners (including dynamic hover/touch)
191
+ */
192
+ destroyEvents() {
193
+ if (this.settings.$closeBtn) {
194
+ const closeEvent = this.settings.eventMode === 'mouseover' ? 'mouseenter' : 'click';
195
+ this.settings.$closeBtn.removeEventListener(closeEvent, e => this.closeEvent(e));
196
+ this.settings.$closeBtn.removeEventListener('keydown', e => this.keyboardEvent(e));
197
+ }
198
+
199
+ if (this.settings.eventMode === 'mouseover') {
200
+ this.$element.removeEventListener('mouseenter', e => this.openEvent(e));
201
+ this.settings.$menuContainer?.removeEventListener('mouseleave', e => this.containerLeaveEvent(e));
202
+ document.removeEventListener('touchstart', e => this.touchStartEvent(e), { passive: true });
203
+ } else {
204
+ this.$element.removeEventListener('click', e => this.toggleEvent(e));
205
+ }
206
+
207
+ this.$element.removeEventListener('keydown', e => this.keyboardEvent(e));
208
+ this.settings.$menu.querySelectorAll('a,button,input,textarea,select')
209
+ .forEach(el => el.removeEventListener('keydown', e => this.keyboardEvent(e)));
210
+ document.removeEventListener('madj2k-flyoutmenu-close', e => this.closeEvent(e));
211
+ document.removeEventListener('madj2k-flyoutmenu-resize', e => this.resizeAndPositionMenuEvent(e));
212
+ }
213
+
214
+
152
215
  /**
153
216
  * Initializes ResizeObserver to auto-resize menu
154
217
  */
@@ -172,37 +235,6 @@ class Madj2kFlyoutMenu {
172
235
  }
173
236
  }
174
237
 
175
- /**
176
- * Unbinds all event listeners
177
- */
178
- destroyEvents() {
179
- if (this.settings.$closeBtn) {
180
- if (this.settings.eventModeClose) {
181
- this.settings.$closeBtn.removeEventListener(this.settings.eventModeClose, e => this.closeEvent(e));
182
- } else {
183
- this.settings.$closeBtn.removeEventListener(this.settings.eventMode, e => this.closeEvent(e));
184
- }
185
- this.settings.$closeBtn.removeEventListener('keydown', e => this.keyboardEvent(e));
186
- }
187
-
188
- if (this.settings.eventModeOpen || this.settings.eventModeClose) {
189
- if (this.settings.eventModeOpen) {
190
- this.$element.removeEventListener(this.settings.eventModeOpen, e => this.openEvent(e));
191
- }
192
- if (this.settings.eventModeClose) {
193
- this.$element.removeEventListener(this.settings.eventModeClose, e => this.closeEvent(e));
194
- }
195
- } else {
196
- this.$element.removeEventListener(this.settings.eventMode, e => this.toggleEvent(e));
197
- }
198
- this.$element.removeEventListener('keydown', e => this.keyboardEvent(e));
199
-
200
- this.settings.$menu.querySelectorAll('a,button,input,textarea,select')
201
- .forEach(el => el.removeEventListener('keydown', e => this.keyboardEvent(e)));
202
-
203
- document.removeEventListener('madj2k-flyoutmenu-close', e => this.closeEvent(e));
204
- document.removeEventListener('madj2k-flyoutmenu-resize', e => this.resizeAndPositionMenuEvent(e));
205
- }
206
238
 
207
239
  /**
208
240
  * Destroys ResizeObserver
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madj2k/fe-frontend-kit",
3
- "version": "2.0.37",
3
+ "version": "2.0.38",
4
4
  "description": "Shared frontend utilities, menus and mixins for projects",
5
5
  "main": "index.js",
6
6
  "style": "index.scss",
package/readme.md CHANGED
@@ -105,15 +105,16 @@ Otherwise in the opened menu the scrolling won't work.
105
105
 
106
106
  ### CSS Class Selectors
107
107
 
108
- | Option | Type | Default | Description |
109
- |--------|------|---------|-------------|
110
- | contentSectionClass | string | 'js-main-content' | Content wrapper used when creating no-scroll helper. |
111
- | menuClass | string | 'js-flyout' | Menu root element class. |
112
- | menuToggleClass | string | 'js-flyout-toggle' | Class for toggle buttons. |
113
- | menuCloseClass | string | 'js-flyout-close' | Class for close buttons inside the flyout. |
114
- | menuContainerClass | string | 'js-flyout-container' | Container used for slide animations (`top` transition). |
115
- | menuInnerClass | string | 'js-flyout-inner' | Inner content wrapper. Observed via ResizeObserver. |
116
- | heightCalculationClass | string | 'calculate' | Temporary class used during height determination. |
108
+ | Option | Type | Default | Description |
109
+ |------------------------|------|-----------------------|-----------------------------------------------------------------|
110
+ | contentSectionClass | string | 'js-main-content' | Content wrapper used when creating no-scroll helper. |
111
+ | menuClass | string | 'js-flyout' | Menu root element class. |
112
+ | menuToggleClass | string | 'js-flyout-toggle' | Class for toggle buttons. |
113
+ | menuCloseClass | string | 'js-flyout-close' | Class for close buttons inside the flyout. |
114
+ | menuContainerClass | string | 'js-flyout-container' | Container used for slide animations (`top` transition). |
115
+ | menuInnerClass | string | 'js-flyout-inner' | Inner content wrapper. Observed via ResizeObserver. |
116
+ | heightCalculationClass | string | 'calculate' | Temporary class used during height determination. |
117
+ | hoverParentClass | string | 'nav-main' | Class of main container of menu (used in eventMode: 'moueover'. |
117
118
 
118
119
  ### Height & Size Behavior
119
120
 
@@ -135,9 +136,7 @@ Otherwise in the opened menu the scrolling won't work.
135
136
 
136
137
  | Option | Type | Default | Description |
137
138
  |--------|------|-------------|
138
- | `eventMode` | `string` | `'click'` | Default event used for toggling the menu. Replaced if `eventModeOpen` or `eventModeClose` are set. |
139
- | `eventModeOpen` | `string` | `''` | Defines a custom event for opening the menu. |
140
- | `eventModeClose` | `string` | `''` | Defines a custom event for closing the menu. |
139
+ | `eventMode` | `string` | `'click'` | Default event used for toggling the menu. Can be set to `click` or `mouseover`. |
141
140
 
142
141
 
143
142
  ## Special: blur/gray effect for background