@madj2k/fe-frontend-kit 2.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/index.js +7 -0
- package/index.scss +6 -0
- package/menus/flyout-menu/flyout-menu-2.0.0.js +331 -0
- package/menus/flyout-menu/flyout-menu-2.0.0.scss +47 -0
- package/menus/flyout-menu/index.js +2 -0
- package/menus/flyout-menu/index.scss +1 -0
- package/menus/pulldown-menu/index.js +2 -0
- package/menus/pulldown-menu/index.scss +1 -0
- package/menus/pulldown-menu/pulldown-menu-2.0.0.js +196 -0
- package/menus/pulldown-menu/pulldown-menu-2.0.0.scss +33 -0
- package/package.json +31 -0
- package/readme.md +218 -0
- package/sass/bootstrap-5.3.0/00_mixins/_accessibility.scss +42 -0
- package/sass/bootstrap-5.3.0/00_mixins/_colors.scss +99 -0
- package/sass/bootstrap-5.3.0/00_mixins/_effects.scss +45 -0
- package/sass/bootstrap-5.3.0/00_mixins/_flex-box.scss +104 -0
- package/sass/bootstrap-5.3.0/00_mixins/_form.scss +164 -0
- package/sass/bootstrap-5.3.0/00_mixins/_format.scss +208 -0
- package/sass/bootstrap-5.3.0/00_mixins/_icons.scss +129 -0
- package/sass/bootstrap-5.3.0/00_mixins/_nav.scss +327 -0
- package/sass/bootstrap-5.3.0/00_mixins/_page.scss +261 -0
- package/sass/bootstrap-5.3.0/00_mixins/_section.scss +111 -0
- package/sass/bootstrap-5.3.0/00_mixins/_toggle-list.scss +133 -0
- package/sass/bootstrap-5.3.0/00_mixins/_unit.scss +51 -0
- package/sass/bootstrap-5.3.0/10_config/_colors.scss +17 -0
- package/sass/bootstrap-5.3.0/10_config/_font.scss +228 -0
- package/sass/bootstrap-5.3.0/10_config/_maps.scss +51 -0
- package/sass/bootstrap-5.3.0/index.scss +20 -0
- package/tools/owl/index.js +2 -0
- package/tools/owl/index.scss +1 -0
- package/tools/owl/owl-thumbnail-2.0.0.js +355 -0
- package/tools/owl/owl-thumbnail-2.0.0.scss +0 -0
- package/tools/resize-end/index.js +2 -0
- package/tools/resize-end/index.scss +1 -0
- package/tools/resize-end/resize-end-2.0.0.js +108 -0
- package/tools/resize-end/resize-end-2.0.0.scss +10 -0
- package/tools/scrolling/index.js +2 -0
- package/tools/scrolling/index.scss +1 -0
- package/tools/scrolling/scrolling-2.0.0.js +244 -0
- package/tools/scrolling/scrolling-2.0.0.scss +10 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResizeEnd Events (Vanilla JS)
|
|
3
|
+
**
|
|
4
|
+
* A lightweight helper class that triggers a debounced 'madj2k-resize-end' event
|
|
5
|
+
* when the user finishes resizing the browser window.
|
|
6
|
+
*
|
|
7
|
+
* It also manages a scrolling detection state (via body attribute) to avoid triggering
|
|
8
|
+
* resize-end during user scrolling, and respects active input fields (useful on mobile).
|
|
9
|
+
*
|
|
10
|
+
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
11
|
+
* @copyright 2025 Steffen Kroggel
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
* @license GNU General Public License v3.0
|
|
14
|
+
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Initialize with defaults
|
|
18
|
+
* const resizeEnd = new Madj2kResizeEnd();
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Initialize with custom config
|
|
22
|
+
* const resizeEnd = new Madj2kResizeEnd({
|
|
23
|
+
* resizeEndTimeout: 300,
|
|
24
|
+
* scrollingEndTimeout: 600
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Listen to resize-end event
|
|
29
|
+
* document.addEventListener('madj2k-resize-end', () => {
|
|
30
|
+
* console.log('Resize-End fired');
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Example use case: reload masonry grid after resize ends
|
|
35
|
+
* document.addEventListener('madj2k-resize-end', () => {
|
|
36
|
+
* myMasonry.reloadItems();
|
|
37
|
+
* myMasonry.layout();
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
class Madj2kResizeEnd {
|
|
41
|
+
|
|
42
|
+
config = {
|
|
43
|
+
scrollingEndTimeout: 500,
|
|
44
|
+
resizeEndTimeout: 200,
|
|
45
|
+
scrollingDataAttr: 'data-resizeend-scrolling'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
finalEventTimers = {};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Constructor
|
|
52
|
+
* @param {Object} config - Optional config overrides
|
|
53
|
+
*/
|
|
54
|
+
constructor(config = {}) {
|
|
55
|
+
this.config = { ...this.config, ...config };
|
|
56
|
+
this.initScrollingDetection();
|
|
57
|
+
this.initResizeEndEvent();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Init resizeEnd custom event
|
|
62
|
+
*/
|
|
63
|
+
initResizeEndEvent() {
|
|
64
|
+
window.addEventListener('resize', () => {
|
|
65
|
+
const body = document.body;
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
!body.hasAttribute(this.config.scrollingDataAttr) ||
|
|
69
|
+
body.getAttribute(this.config.scrollingDataAttr) === '0'
|
|
70
|
+
) {
|
|
71
|
+
this.waitForFinalEvent(() => {
|
|
72
|
+
// Skip if input is focused (e.g. keyboard open on mobile)
|
|
73
|
+
const active = document.activeElement;
|
|
74
|
+
if (!(active && active.tagName === 'INPUT')) {
|
|
75
|
+
const event = new CustomEvent('madj2k-resize-end');
|
|
76
|
+
document.dispatchEvent(event);
|
|
77
|
+
}
|
|
78
|
+
}, this.config.resizeEndTimeout, 'resize');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Init scrolling detection
|
|
85
|
+
*/
|
|
86
|
+
initScrollingDetection() {
|
|
87
|
+
const handler = () => {
|
|
88
|
+
document.body.setAttribute(this.config.scrollingDataAttr, '1');
|
|
89
|
+
|
|
90
|
+
this.waitForFinalEvent(() => {
|
|
91
|
+
document.body.setAttribute(this.config.scrollingDataAttr, '0');
|
|
92
|
+
}, this.config.scrollingEndTimeout, 'scrolling');
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
window.addEventListener('scroll', handler, { passive: true });
|
|
96
|
+
window.addEventListener('touchmove', handler, { passive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Debounced final event dispatcher
|
|
101
|
+
*/
|
|
102
|
+
waitForFinalEvent(callback, ms, uniqueId = "default") {
|
|
103
|
+
if (this.finalEventTimers[uniqueId]) {
|
|
104
|
+
clearTimeout(this.finalEventTimers[uniqueId]);
|
|
105
|
+
}
|
|
106
|
+
this.finalEventTimers[uniqueId] = setTimeout(callback, ms);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@forward './scrolling-2.0.0';
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolling-Events (Vanilla JS)
|
|
3
|
+
*
|
|
4
|
+
* A lightweight scrolling helper class that enables:
|
|
5
|
+
* 1. Body classes based on scroll direction (scroll-up / scroll-down)
|
|
6
|
+
* 2. Smooth anchor scrolling with optional offset
|
|
7
|
+
* 3. Automatic scrolling when collapsible elements (like Bootstrap .collapse) open
|
|
8
|
+
* 4. Appear-on-scroll animations for elements
|
|
9
|
+
*
|
|
10
|
+
* The class is fully configurable via options and is designed to be used in CMS contexts
|
|
11
|
+
* where elements can be added, removed or re-ordered dynamically.
|
|
12
|
+
*
|
|
13
|
+
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
14
|
+
* @copyright 2025 Steffen Kroggel
|
|
15
|
+
* @version 2.0.0
|
|
16
|
+
* @license GNU General Public License v3.0
|
|
17
|
+
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Initialize with defaults
|
|
21
|
+
* const scrolling = new Scrolling();
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Initialize with custom config
|
|
25
|
+
* const scrolling = new Madj2kScrolling({
|
|
26
|
+
* anchorScrollingCollapsibleSelector: ['.collapse', '.custom-collapse'],
|
|
27
|
+
* anchorScrollingSelector: ['a[href^="#"]', '.btn-scroll'],*
|
|
28
|
+
* anchorScrollingOffsetSelector: '#siteheader',
|
|
29
|
+
* anchorScrollingDisableClass: 'js-no-scroll',
|
|
30
|
+
* appearOnScrollSelector: ['.js-appear-on-scroll'],
|
|
31
|
+
* appearOnScrollTimeout: 500,
|
|
32
|
+
* appearOnScrollThreshold: 25*
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Example HTML for anchor scrolling:
|
|
37
|
+
* <a href="#section1">Go to Section 1</a>
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Example HTML for anchor scrolling with collapsibles:
|
|
41
|
+
* <div class="collapse" id="accordion1">...</div>
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Example HTML for appear on scroll:
|
|
45
|
+
* <div class="js-appear-on-scroll">
|
|
46
|
+
* <h2>Animated content</h2>
|
|
47
|
+
* <p>This will fade and move in when scrolled into view.</p>
|
|
48
|
+
* </div>
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Suggested SCSS for appear on scroll:
|
|
52
|
+
* .js-appear-on-scroll {
|
|
53
|
+
* opacity: 0;
|
|
54
|
+
* transition: opacity 0.5s ease-out, transform 0.5s ease-out;
|
|
55
|
+
* transform: translateY(1rem);
|
|
56
|
+
*
|
|
57
|
+
* &[data-appear-on-scroll="0"] {
|
|
58
|
+
* opacity: 1;
|
|
59
|
+
* transform: translateY(0);
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
class Madj2kScrolling {
|
|
65
|
+
config = {
|
|
66
|
+
anchorScrollingSelector: ['a[href^="#"]'],
|
|
67
|
+
anchorScrollingOffsetSelector: '',
|
|
68
|
+
anchorScrollingScriptScrollTimeout: 800,
|
|
69
|
+
anchorScrollingDisableClass: 'js-no-scroll',
|
|
70
|
+
anchorScrollingCollapsibleSelector: ['.collapse'],
|
|
71
|
+
anchorScrollingBehavior: 'smooth',
|
|
72
|
+
appearOnScrollSelector: ['.js-appear-on-scroll'],
|
|
73
|
+
appearOnScrollTimeout: 500,
|
|
74
|
+
appearOnScrollThreshold: 25
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
constructor(config) {
|
|
78
|
+
this.config = { ...this.config, ...config };
|
|
79
|
+
this.scrollTimer = null;
|
|
80
|
+
|
|
81
|
+
this.initScrollClassesForBody();
|
|
82
|
+
this.initAnchorScrolling();
|
|
83
|
+
this.initAppearOnScroll();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
initScrollClassesForBody() {
|
|
87
|
+
const body = document.body;
|
|
88
|
+
let lastScrollTop = window.scrollY;
|
|
89
|
+
let lastContentHeight = document.documentElement.scrollHeight;
|
|
90
|
+
|
|
91
|
+
const setContentHeight = () => {
|
|
92
|
+
lastContentHeight = document.documentElement.scrollHeight;
|
|
93
|
+
body.setAttribute('data-last-content-height', lastContentHeight);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const addScrollClasses = () => {
|
|
97
|
+
const scrollTop = window.scrollY;
|
|
98
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
99
|
+
|
|
100
|
+
if (parseInt(body.getAttribute('data-last-content-height')) !== contentHeight) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!body.classList.contains('block-scroll-classes')) {
|
|
105
|
+
if (scrollTop > 0) {
|
|
106
|
+
body.classList.remove('scroll-up', 'scroll-down');
|
|
107
|
+
if (scrollTop > lastScrollTop) {
|
|
108
|
+
body.classList.add('scroll-down');
|
|
109
|
+
} else if (scrollTop < lastScrollTop) {
|
|
110
|
+
body.classList.add('scroll-up');
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
body.classList.remove('scroll-down');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
body.setAttribute('data-last-scroll-top', Math.max(scrollTop, 0));
|
|
118
|
+
lastScrollTop = scrollTop;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
setContentHeight();
|
|
122
|
+
|
|
123
|
+
window.addEventListener('scroll', () => {
|
|
124
|
+
setContentHeight();
|
|
125
|
+
addScrollClasses();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
initAnchorScrolling() {
|
|
130
|
+
const offsetElement = document.querySelector(this.config.anchorScrollingOffsetSelector);
|
|
131
|
+
let scriptScrollTimer = null;
|
|
132
|
+
|
|
133
|
+
const scrollToElement = (element) => {
|
|
134
|
+
if (element) {
|
|
135
|
+
const rect = element.getBoundingClientRect();
|
|
136
|
+
let scrollTo = window.scrollY + rect.top - 40;
|
|
137
|
+
|
|
138
|
+
if (offsetElement && offsetElement.offsetTop >= 0 && !offsetElement.hidden) {
|
|
139
|
+
scrollTo -= offsetElement.offsetHeight;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
document.body.classList.add('block-scroll-classes');
|
|
143
|
+
|
|
144
|
+
if (scriptScrollTimer) {
|
|
145
|
+
clearTimeout(scriptScrollTimer);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
scriptScrollTimer = setTimeout(() => {
|
|
149
|
+
document.body.classList.remove('block-scroll-classes');
|
|
150
|
+
}, this.config.anchorScrollingScriptScrollTimeout);
|
|
151
|
+
|
|
152
|
+
window.scrollTo({
|
|
153
|
+
top: scrollTo,
|
|
154
|
+
behavior: this.config.anchorScrollingBehavior
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const jumpToAnchorByUrl = () => {
|
|
160
|
+
const anchorName = window.location.hash.replace('#', '');
|
|
161
|
+
if (anchorName) {
|
|
162
|
+
const anchor = document.querySelector(`a[id="${anchorName}"], #${anchorName}`);
|
|
163
|
+
if (anchor) scrollToElement(anchor);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const jumpToAnchorByLink = (event) => {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
|
|
170
|
+
const anchorId = event.currentTarget.getAttribute('href');
|
|
171
|
+
|
|
172
|
+
if (anchorId && anchorId.startsWith('#')) {
|
|
173
|
+
const anchor = document.querySelector(anchorId);
|
|
174
|
+
if (anchor) scrollToElement(anchor);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const getAnchorSelector = () => {
|
|
179
|
+
return this.config.anchorScrollingSelector
|
|
180
|
+
.map(sel => `${sel}:not(.visually-hidden-focusable):not(.${this.config.anchorScrollingDisableClass})`)
|
|
181
|
+
.join(', ');
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const getCollapsibleSelector = () => {
|
|
185
|
+
return this.config.anchorScrollingCollapsibleSelector
|
|
186
|
+
.map(sel => `${sel}:not(.${this.config.anchorScrollingDisableClass})`)
|
|
187
|
+
.join(', ');
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
document.querySelectorAll(getAnchorSelector())
|
|
191
|
+
.forEach(link => {
|
|
192
|
+
link.addEventListener('click', jumpToAnchorByLink);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
document.querySelectorAll(getCollapsibleSelector())
|
|
196
|
+
.forEach(el => {
|
|
197
|
+
el.addEventListener('shown.bs.collapse', e => {
|
|
198
|
+
scrollToElement(e.target);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
jumpToAnchorByUrl();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
initAppearOnScroll() {
|
|
206
|
+
const initElement = (element) => {
|
|
207
|
+
const rect = element.getBoundingClientRect();
|
|
208
|
+
const windowBottom = window.scrollY + window.innerHeight;
|
|
209
|
+
const elementTop = window.scrollY + rect.top;
|
|
210
|
+
|
|
211
|
+
if (windowBottom > elementTop) {
|
|
212
|
+
element.setAttribute('data-appear-on-scroll', 0);
|
|
213
|
+
} else {
|
|
214
|
+
element.setAttribute('data-appear-on-scroll', 1);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const showElement = (element) => {
|
|
219
|
+
const rect = element.getBoundingClientRect();
|
|
220
|
+
const windowBottom = window.scrollY + window.innerHeight;
|
|
221
|
+
const elementTop = window.scrollY + rect.top;
|
|
222
|
+
|
|
223
|
+
if (windowBottom > elementTop) {
|
|
224
|
+
element.setAttribute('data-appear-on-scroll', 0);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const getAppearOnScrollSelector = () => {
|
|
229
|
+
return this.config.appearOnScrollSelector.join(', ');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const updateOnScroll = () => {
|
|
233
|
+
document.querySelectorAll(getAppearOnScrollSelector()).forEach(element => {
|
|
234
|
+
showElement(element);
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
document.querySelectorAll(getAppearOnScrollSelector()).forEach(element => {
|
|
239
|
+
initElement(element);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
window.addEventListener('scroll', updateOnScroll);
|
|
243
|
+
}
|
|
244
|
+
}
|