@madj2k/fe-frontend-kit 2.0.36 → 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.1
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,13 @@ 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',
55
+ eventMode: 'click',
54
56
  paddingBehavior: 0,
55
57
  paddingViewPortMinWidth: 0,
56
- animationDuration: 500
58
+ animationDuration: 500,
59
+ scrollHelper: true,
57
60
  };
58
61
 
59
62
  this.settings = Object.assign({}, defaults, options);
@@ -73,6 +76,8 @@ class Madj2kFlyoutMenu {
73
76
  this.settings.$menuContainer = this.settings.$menu.querySelector(`.${this.settings.menuContainerClass}`);
74
77
  this.settings.$menuInner = this.settings.$menu.querySelector(`.${this.settings.menuInnerClass}`);
75
78
 
79
+ // Bind persistent event handlers
80
+
76
81
  this.initNoScrollHelper();
77
82
  this.resizeAndPositionMenu();
78
83
  this.paddingMenu();
@@ -111,25 +116,102 @@ class Madj2kFlyoutMenu {
111
116
  }
112
117
  }
113
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
+
114
162
  /**
115
163
  * Binds all necessary event listeners
116
164
  */
117
165
  bindEvents() {
118
166
  if (this.settings.$closeBtn) {
119
- this.settings.$closeBtn.addEventListener('click', e => this.closeEvent(e));
167
+ const closeEvent = this.settings.eventMode === 'mouseover' ? 'mouseenter' : 'click';
168
+ this.settings.$closeBtn.addEventListener(closeEvent, e => this.closeEvent(e));
120
169
  this.settings.$closeBtn.addEventListener('keydown', e => this.keyboardEvent(e));
121
170
  }
122
171
 
123
- this.$element.addEventListener('click', e => this.toggleEvent(e));
124
- this.$element.addEventListener('keydown', e => this.keyboardEvent(e));
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 });
177
+ } else {
178
+ this.$element.addEventListener('click', e => this.toggleEvent(e));
179
+ }
125
180
 
181
+ this.$element.addEventListener('keydown', e => this.keyboardEvent(e));
126
182
  this.settings.$menu.querySelectorAll('a,button,input,textarea,select')
127
183
  .forEach(el => el.addEventListener('keydown', e => this.keyboardEvent(e)));
128
-
129
184
  document.addEventListener('madj2k-flyoutmenu-close', e => this.closeEvent(e));
130
185
  document.addEventListener('madj2k-flyoutmenu-resize', e => this.resizeAndPositionMenuEvent(e));
131
186
  }
132
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
+
133
215
  /**
134
216
  * Initializes ResizeObserver to auto-resize menu
135
217
  */
@@ -153,24 +235,6 @@ class Madj2kFlyoutMenu {
153
235
  }
154
236
  }
155
237
 
156
- /**
157
- * Unbinds all event listeners
158
- */
159
- destroyEvents() {
160
- if (this.settings.$closeBtn) {
161
- this.settings.$closeBtn.removeEventListener('click', e => this.closeEvent(e));
162
- this.settings.$closeBtn.removeEventListener('keydown', e => this.keyboardEvent(e));
163
- }
164
-
165
- this.$element.removeEventListener('click', e => this.toggleEvent(e));
166
- this.$element.removeEventListener('keydown', e => this.keyboardEvent(e));
167
-
168
- this.settings.$menu.querySelectorAll('a,button,input,textarea,select')
169
- .forEach(el => el.removeEventListener('keydown', e => this.keyboardEvent(e)));
170
-
171
- document.removeEventListener('madj2k-flyoutmenu-close', e => this.closeEvent(e));
172
- document.removeEventListener('madj2k-flyoutmenu-resize', e => this.resizeAndPositionMenuEvent(e));
173
- }
174
238
 
175
239
  /**
176
240
  * Destroys ResizeObserver
@@ -227,6 +291,16 @@ class Madj2kFlyoutMenu {
227
291
  }
228
292
 
229
293
 
294
+ /**
295
+ * Handles close event
296
+ * @param {Event} e - The close event
297
+ */
298
+ openEvent(e) {
299
+ e.preventDefault();
300
+ this.open();
301
+ }
302
+
303
+
230
304
  /**
231
305
  * Opens the flyout menu
232
306
  */
@@ -408,15 +482,19 @@ class Madj2kFlyoutMenu {
408
482
  * Initializes the no-scroll helper elements
409
483
  */
410
484
  initNoScrollHelper() {
411
- const body = document.body;
412
- let helper = body.querySelector('.no-scroll-helper');
413
- const content = document.querySelector(`.${this.settings.contentSectionClass}`);
414
485
 
415
- if (!helper) {
416
- if (content) {
417
- content.innerHTML = `<div class="no-scroll-helper"><div class="no-scroll-helper-inner">${content.innerHTML}</div></div>`;
418
- } else {
419
- body.innerHTML = `<div class="no-scroll-helper"><div class="no-scroll-helper-inner">${body.innerHTML}</div></div>`;
486
+ // heightMode "full" with deprecated fullHeight-setting as fallback
487
+ if (this.settings.scrollHelper) {
488
+ const body = document.body;
489
+ let helper = body.querySelector('.no-scroll-helper');
490
+ const content = document.querySelector(`.${this.settings.contentSectionClass}`);
491
+
492
+ if (!helper) {
493
+ if (content) {
494
+ content.innerHTML = `<div class="no-scroll-helper"><div class="no-scroll-helper-inner">${content.innerHTML}</div></div>`;
495
+ } else {
496
+ body.innerHTML = `<div class="no-scroll-helper"><div class="no-scroll-helper-inner">${body.innerHTML}</div></div>`;
497
+ }
420
498
  }
421
499
  }
422
500
  }
@@ -451,7 +529,6 @@ class Madj2kFlyoutMenu {
451
529
  body.classList.remove(this.settings.openStatusBodyClassOverflow);
452
530
  window.scrollTo({top: scrollTop, behavior: 'instant'});
453
531
  }
454
-
455
532
  }
456
533
  }
457
534
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madj2k/fe-frontend-kit",
3
- "version": "2.0.36",
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
@@ -79,7 +79,7 @@ CSS:
79
79
  </nav>
80
80
  </div>
81
81
  ```
82
- IMPORTANT: If the siteheader is positioned with ```position:fixed```, you have to switch that to ```position:absolute``` when the menu is opened.
82
+ IMPORTANT: If the siteheader is positioned with ```position:fixed``` and you are using full-height-mode (which is the default) you have to switch that to ```position:absolute``` when the menu is opened.
83
83
  Otherwise in the opened menu the scrolling won't work.
84
84
  ```
85
85
  .flyout-open {
@@ -88,6 +88,57 @@ Otherwise in the opened menu the scrolling won't work.
88
88
  }
89
89
  }
90
90
  ```
91
+
92
+
93
+ ## Options Reference
94
+
95
+ ### State & Animation Classes
96
+
97
+ | Option | Type | Default | Description |
98
+ |--------|------|---------|-------------|
99
+ | openStatusClass | string | 'open' | Applied to trigger and menu when open. |
100
+ | animationOpenStatusClass | string | 'opening' | Applied during opening animation. |
101
+ | animationCloseStatusClass | string | 'closing' | Applied during closing animation. |
102
+ | animationBodyClassPrefix | string | 'flyout' | Prefix for body animation classes like `flyout-opening`. |
103
+ | openStatusBodyClass | string | 'flyout-open' | Applied to `body` when the flyout is open. |
104
+ | openStatusBodyClassOverflow | string | 'flyout-open-overflow' | Applied when page height exceeds viewport (used in scroll-locking). |
105
+
106
+ ### CSS Class Selectors
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. |
117
+ | hoverParentClass | string | 'nav-main' | Class of main container of menu (used in eventMode: 'moueover'. |
118
+
119
+ ### Height & Size Behavior
120
+
121
+ | Option | Type | Default | Description |
122
+ |--------|------|---------|-------------|
123
+ | heightMode | 'full' \| 'maxContent' | 'full' | Determines height behavior of the flyout. |
124
+ | animationDuration | number | 500 | Animation duration in milliseconds. |
125
+
126
+ ### Padding & Layout Behavior
127
+
128
+ | Option | Type | Default | Description |
129
+ |--------|------|---------|-------------|
130
+ | paddingBehavior | number | 0 | Controls dynamic horizontal padding. |
131
+ | paddingViewPortMinWidth | number | 0 | Minimum viewport width required before padding applies. |
132
+ | scrollHelper | boolean | true | Creates additional wrapper structure to enable scroll-locking. |
133
+
134
+
135
+ ### Event Handling
136
+
137
+ | Option | Type | Default | Description |
138
+ |--------|------|-------------|
139
+ | `eventMode` | `string` | `'click'` | Default event used for toggling the menu. Can be set to `click` or `mouseover`. |
140
+
141
+
91
142
  ## Special: blur/gray effect for background
92
143
  * In order to achieve a blur/gray-effect for the background we add the following DIV to the main-content section:
93
144
  ```
@@ -199,6 +250,36 @@ CSS:
199
250
  ```
200
251
 
201
252
 
253
+ ## Options Reference
254
+
255
+ ### State & Structural Classes
256
+
257
+ | Option | Type | Default | Description |
258
+ |--------|------|---------|-------------|
259
+ | **openStatusClass** | `string` | `'open'` | Applied to menu, wrapper, and toggle when the pulldown is open. |
260
+ | **menuClass** | `string` | `'js-pulldown'` | Root class of the pulldown menu. Must match your HTML structure. |
261
+ | **menuToggleClass** | `string` | `'js-pulldown-toggle'` | Toggle buttons that control pulldown menus and also close others. |
262
+ | **menuWrapClass** | `string` | `'js-pulldown-wrap'` | Optional wrapper container that receives open/closed states. |
263
+
264
+
265
+ ### Animation & Timing
266
+
267
+ | Option | Type | Default | Description |
268
+ |--------|------|---------|-------------|
269
+ | **animationDuration** | `number` | `500` | Reserved for future open/close animation timing. Currently no CSS transition is applied by JavaScript. |
270
+
271
+
272
+ ### Internal Element References
273
+
274
+ These are automatically detected:
275
+
276
+ | Property | Description |
277
+ |----------|-------------|
278
+ | **menu** | The menu element referenced via `aria-controls`. |
279
+ | **menuWrap** | Closest parent using `menuWrapClass`. |
280
+ | **toggleElement** | The trigger element passed to the constructor. |
281
+
282
+
202
283
  # JS: Banner
203
284
  A lightweight class to show a full-page overlay (banner, popup, hint or cookie layer),
204
285
  with opening and closing animation and optional cookie persistence.