@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.
Files changed (40) hide show
  1. package/index.js +7 -0
  2. package/index.scss +6 -0
  3. package/menus/flyout-menu/flyout-menu-2.0.0.js +331 -0
  4. package/menus/flyout-menu/flyout-menu-2.0.0.scss +47 -0
  5. package/menus/flyout-menu/index.js +2 -0
  6. package/menus/flyout-menu/index.scss +1 -0
  7. package/menus/pulldown-menu/index.js +2 -0
  8. package/menus/pulldown-menu/index.scss +1 -0
  9. package/menus/pulldown-menu/pulldown-menu-2.0.0.js +196 -0
  10. package/menus/pulldown-menu/pulldown-menu-2.0.0.scss +33 -0
  11. package/package.json +31 -0
  12. package/readme.md +218 -0
  13. package/sass/bootstrap-5.3.0/00_mixins/_accessibility.scss +42 -0
  14. package/sass/bootstrap-5.3.0/00_mixins/_colors.scss +99 -0
  15. package/sass/bootstrap-5.3.0/00_mixins/_effects.scss +45 -0
  16. package/sass/bootstrap-5.3.0/00_mixins/_flex-box.scss +104 -0
  17. package/sass/bootstrap-5.3.0/00_mixins/_form.scss +164 -0
  18. package/sass/bootstrap-5.3.0/00_mixins/_format.scss +208 -0
  19. package/sass/bootstrap-5.3.0/00_mixins/_icons.scss +129 -0
  20. package/sass/bootstrap-5.3.0/00_mixins/_nav.scss +327 -0
  21. package/sass/bootstrap-5.3.0/00_mixins/_page.scss +261 -0
  22. package/sass/bootstrap-5.3.0/00_mixins/_section.scss +111 -0
  23. package/sass/bootstrap-5.3.0/00_mixins/_toggle-list.scss +133 -0
  24. package/sass/bootstrap-5.3.0/00_mixins/_unit.scss +51 -0
  25. package/sass/bootstrap-5.3.0/10_config/_colors.scss +17 -0
  26. package/sass/bootstrap-5.3.0/10_config/_font.scss +228 -0
  27. package/sass/bootstrap-5.3.0/10_config/_maps.scss +51 -0
  28. package/sass/bootstrap-5.3.0/index.scss +20 -0
  29. package/tools/owl/index.js +2 -0
  30. package/tools/owl/index.scss +1 -0
  31. package/tools/owl/owl-thumbnail-2.0.0.js +355 -0
  32. package/tools/owl/owl-thumbnail-2.0.0.scss +0 -0
  33. package/tools/resize-end/index.js +2 -0
  34. package/tools/resize-end/index.scss +1 -0
  35. package/tools/resize-end/resize-end-2.0.0.js +108 -0
  36. package/tools/resize-end/resize-end-2.0.0.scss +10 -0
  37. package/tools/scrolling/index.js +2 -0
  38. package/tools/scrolling/index.scss +1 -0
  39. package/tools/scrolling/scrolling-2.0.0.js +244 -0
  40. 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,10 @@
1
+ .js-appear-on-scroll {
2
+ opacity: 0;
3
+ transition: opacity 0.5s ease-out, transform 0.5s ease-out;
4
+ transform: translateY(1rem);
5
+
6
+ &[data-appear-on-scroll="0"] {
7
+ opacity: 1;
8
+ transform: translateY(0);
9
+ }
10
+ }
@@ -0,0 +1,2 @@
1
+ import Madj2kScrolling from './scrolling-2.0.0.js';
2
+ export { Madj2kScrolling };
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ .js-appear-on-scroll {
2
+ opacity: 0;
3
+ transition: opacity 0.5s ease-out, transform 0.5s ease-out;
4
+ transform: translateY(1rem);
5
+
6
+ &[data-appear-on-scroll="0"] {
7
+ opacity: 1;
8
+ transform: translateY(0);
9
+ }
10
+ }