vanduo-framework 1.1.8-docs-update

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 (196) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +216 -0
  3. package/css/components/alerts.css +224 -0
  4. package/css/components/avatar.css +275 -0
  5. package/css/components/badges.css +230 -0
  6. package/css/components/breadcrumbs.css +146 -0
  7. package/css/components/button-group.css +82 -0
  8. package/css/components/buttons.css +530 -0
  9. package/css/components/cards.css +304 -0
  10. package/css/components/chips.css +259 -0
  11. package/css/components/code-snippet.css +555 -0
  12. package/css/components/collapsible.css +267 -0
  13. package/css/components/collections.css +253 -0
  14. package/css/components/doc-search.css +464 -0
  15. package/css/components/doc-tabs.css +38 -0
  16. package/css/components/draggable.css +317 -0
  17. package/css/components/dropdown.css +266 -0
  18. package/css/components/footer.css +375 -0
  19. package/css/components/forms.css +1774 -0
  20. package/css/components/image-box.css +279 -0
  21. package/css/components/modals.css +285 -0
  22. package/css/components/navbar.css +530 -0
  23. package/css/components/pagination.css +186 -0
  24. package/css/components/preloader.css +340 -0
  25. package/css/components/progress.css +107 -0
  26. package/css/components/sidenav.css +301 -0
  27. package/css/components/skeleton.css +241 -0
  28. package/css/components/spinner.css +144 -0
  29. package/css/components/tabs.css +327 -0
  30. package/css/components/theme-customizer.css +835 -0
  31. package/css/components/toast.css +357 -0
  32. package/css/components/tooltips.css +270 -0
  33. package/css/core/colors.css +1017 -0
  34. package/css/core/fonts.css +266 -0
  35. package/css/core/grid.css +1699 -0
  36. package/css/core/helpers.css +2202 -0
  37. package/css/core/reset.css +128 -0
  38. package/css/core/tokens.css +213 -0
  39. package/css/core/typography.css +405 -0
  40. package/css/core/vd-aliases.css +47 -0
  41. package/css/effects/parallax.css +113 -0
  42. package/css/icons/icons-all.css +23 -0
  43. package/css/icons/icons.css +25 -0
  44. package/css/utilities/media.css +167 -0
  45. package/css/utilities/print.css +111 -0
  46. package/css/utilities/shadow.css +243 -0
  47. package/css/utilities/table.css +381 -0
  48. package/css/utilities/transforms.css +71 -0
  49. package/css/utilities/transitions.css +87 -0
  50. package/css/vanduo.css +80 -0
  51. package/dist/build-info.json +6 -0
  52. package/dist/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
  53. package/dist/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
  54. package/dist/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
  55. package/dist/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
  56. package/dist/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
  57. package/dist/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
  58. package/dist/fonts/inter/inter-bold.woff2 +0 -0
  59. package/dist/fonts/inter/inter-medium.woff2 +0 -0
  60. package/dist/fonts/inter/inter-regular.woff2 +0 -0
  61. package/dist/fonts/inter/inter-semibold.woff2 +0 -0
  62. package/dist/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
  63. package/dist/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
  64. package/dist/fonts/open-sans/open-sans-bold.woff2 +0 -0
  65. package/dist/fonts/open-sans/open-sans-medium.woff2 +0 -0
  66. package/dist/fonts/open-sans/open-sans-regular.woff2 +0 -0
  67. package/dist/fonts/rubik/rubik-bold.woff2 +0 -0
  68. package/dist/fonts/rubik/rubik-medium.woff2 +0 -0
  69. package/dist/fonts/rubik/rubik-regular.woff2 +0 -0
  70. package/dist/fonts/source-sans/source-sans-bold.woff2 +0 -0
  71. package/dist/fonts/source-sans/source-sans-regular.woff2 +0 -0
  72. package/dist/fonts/source-sans/source-sans-semibold.woff2 +0 -0
  73. package/dist/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
  74. package/dist/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
  75. package/dist/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
  76. package/dist/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
  77. package/dist/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
  78. package/dist/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
  79. package/dist/icons/phosphor/LICENSE +21 -0
  80. package/dist/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
  81. package/dist/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
  82. package/dist/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
  83. package/dist/icons/phosphor/bold/style.css +4627 -0
  84. package/dist/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
  85. package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
  86. package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
  87. package/dist/icons/phosphor/duotone/style.css +12115 -0
  88. package/dist/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
  89. package/dist/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
  90. package/dist/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
  91. package/dist/icons/phosphor/fill/style.css +4627 -0
  92. package/dist/icons/phosphor/light/Phosphor-Light.ttf +0 -0
  93. package/dist/icons/phosphor/light/Phosphor-Light.woff +0 -0
  94. package/dist/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
  95. package/dist/icons/phosphor/light/style.css +4627 -0
  96. package/dist/icons/phosphor/regular/Phosphor.ttf +0 -0
  97. package/dist/icons/phosphor/regular/Phosphor.woff +0 -0
  98. package/dist/icons/phosphor/regular/Phosphor.woff2 +0 -0
  99. package/dist/icons/phosphor/regular/style.css +4627 -0
  100. package/dist/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
  101. package/dist/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
  102. package/dist/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
  103. package/dist/icons/phosphor/thin/style.css +4627 -0
  104. package/dist/vanduo.cjs.js +5569 -0
  105. package/dist/vanduo.cjs.js.map +7 -0
  106. package/dist/vanduo.cjs.min.js +48 -0
  107. package/dist/vanduo.cjs.min.js.map +7 -0
  108. package/dist/vanduo.css +60666 -0
  109. package/dist/vanduo.css.map +1 -0
  110. package/dist/vanduo.esm.js +5548 -0
  111. package/dist/vanduo.esm.js.map +7 -0
  112. package/dist/vanduo.esm.min.js +48 -0
  113. package/dist/vanduo.esm.min.js.map +7 -0
  114. package/dist/vanduo.js +5545 -0
  115. package/dist/vanduo.js.map +7 -0
  116. package/dist/vanduo.min.css +2 -0
  117. package/dist/vanduo.min.css.map +1 -0
  118. package/dist/vanduo.min.js +48 -0
  119. package/dist/vanduo.min.js.map +7 -0
  120. package/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
  121. package/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
  122. package/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
  123. package/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
  124. package/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
  125. package/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
  126. package/fonts/inter/inter-bold.woff2 +0 -0
  127. package/fonts/inter/inter-medium.woff2 +0 -0
  128. package/fonts/inter/inter-regular.woff2 +0 -0
  129. package/fonts/inter/inter-semibold.woff2 +0 -0
  130. package/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
  131. package/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
  132. package/fonts/open-sans/open-sans-bold.woff2 +0 -0
  133. package/fonts/open-sans/open-sans-medium.woff2 +0 -0
  134. package/fonts/open-sans/open-sans-regular.woff2 +0 -0
  135. package/fonts/rubik/rubik-bold.woff2 +0 -0
  136. package/fonts/rubik/rubik-medium.woff2 +0 -0
  137. package/fonts/rubik/rubik-regular.woff2 +0 -0
  138. package/fonts/source-sans/source-sans-bold.woff2 +0 -0
  139. package/fonts/source-sans/source-sans-regular.woff2 +0 -0
  140. package/fonts/source-sans/source-sans-semibold.woff2 +0 -0
  141. package/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
  142. package/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
  143. package/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
  144. package/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
  145. package/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
  146. package/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
  147. package/icons/phosphor/LICENSE +21 -0
  148. package/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
  149. package/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
  150. package/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
  151. package/icons/phosphor/bold/style.css +4627 -0
  152. package/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
  153. package/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
  154. package/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
  155. package/icons/phosphor/duotone/style.css +12115 -0
  156. package/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
  157. package/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
  158. package/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
  159. package/icons/phosphor/fill/style.css +4627 -0
  160. package/icons/phosphor/light/Phosphor-Light.ttf +0 -0
  161. package/icons/phosphor/light/Phosphor-Light.woff +0 -0
  162. package/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
  163. package/icons/phosphor/light/style.css +4627 -0
  164. package/icons/phosphor/regular/Phosphor.ttf +0 -0
  165. package/icons/phosphor/regular/Phosphor.woff +0 -0
  166. package/icons/phosphor/regular/Phosphor.woff2 +0 -0
  167. package/icons/phosphor/regular/style.css +4627 -0
  168. package/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
  169. package/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
  170. package/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
  171. package/icons/phosphor/thin/style.css +4627 -0
  172. package/js/components/code-snippet.js +639 -0
  173. package/js/components/collapsible.js +226 -0
  174. package/js/components/doc-search.js +936 -0
  175. package/js/components/draggable.js +725 -0
  176. package/js/components/dropdown.js +362 -0
  177. package/js/components/font-switcher.js +253 -0
  178. package/js/components/grid.js +279 -0
  179. package/js/components/image-box.js +372 -0
  180. package/js/components/modals.js +367 -0
  181. package/js/components/navbar.js +264 -0
  182. package/js/components/pagination.js +286 -0
  183. package/js/components/parallax.js +216 -0
  184. package/js/components/preloader.js +183 -0
  185. package/js/components/select.js +444 -0
  186. package/js/components/sidenav.js +303 -0
  187. package/js/components/tabs.js +303 -0
  188. package/js/components/theme-customizer.js +784 -0
  189. package/js/components/theme-switcher.js +183 -0
  190. package/js/components/toast.js +343 -0
  191. package/js/components/tooltips.js +306 -0
  192. package/js/index.js +52 -0
  193. package/js/utils/helpers.js +306 -0
  194. package/js/utils/lifecycle.js +135 -0
  195. package/js/vanduo.js +120 -0
  196. package/package.json +78 -0
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Vanduo Framework - Modals Component
3
+ * JavaScript functionality for modal dialogs
4
+ */
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Modals Component
11
+ */
12
+ const Modals = {
13
+ modals: new Map(),
14
+ openModals: [],
15
+ zIndexCounter: 1050,
16
+
17
+ // Store trigger cleanup functions
18
+ _triggerCleanups: [],
19
+
20
+ /**
21
+ * Initialize modals
22
+ */
23
+ init: function () {
24
+ const modals = document.querySelectorAll('.vd-modal');
25
+
26
+ modals.forEach(modal => {
27
+ if (this.modals.has(modal)) {
28
+ return;
29
+ }
30
+ this.initModal(modal);
31
+ });
32
+
33
+ // Handle data-modal triggers
34
+ const triggers = document.querySelectorAll('[data-modal]');
35
+ triggers.forEach(trigger => {
36
+ if (trigger.dataset.modalTriggerInitialized) return;
37
+ trigger.dataset.modalTriggerInitialized = 'true';
38
+
39
+ const triggerClickHandler = (e) => {
40
+ e.preventDefault();
41
+ const modalId = trigger.dataset.modal;
42
+ const modal = document.querySelector(modalId);
43
+ if (modal) {
44
+ this.open(modal);
45
+ }
46
+ };
47
+ trigger.addEventListener('click', triggerClickHandler);
48
+ this._triggerCleanups.push(() => trigger.removeEventListener('click', triggerClickHandler));
49
+ });
50
+ },
51
+
52
+ /**
53
+ * Initialize a single modal
54
+ * @param {HTMLElement} modal - Modal element
55
+ */
56
+ initModal: function (modal) {
57
+ const backdrop = this.createBackdrop(modal);
58
+ const closeButtons = modal.querySelectorAll('.vd-modal-close, [data-dismiss="modal"]');
59
+ const dialog = modal.querySelector('.vd-modal-dialog');
60
+
61
+ if (!dialog) {
62
+ return;
63
+ }
64
+
65
+ const cleanupFunctions = [];
66
+
67
+ // Set ARIA attributes
68
+ modal.setAttribute('role', 'dialog');
69
+ modal.setAttribute('aria-modal', 'true');
70
+ modal.setAttribute('aria-hidden', 'true');
71
+
72
+ // Generate ID if not exists
73
+ if (!modal.id) {
74
+ modal.id = 'modal-' + Math.random().toString(36).substr(2, 9);
75
+ }
76
+
77
+ // Set aria-labelledby
78
+ const title = modal.querySelector('.vd-modal-title');
79
+ if (title && !title.id) {
80
+ title.id = modal.id + '-title';
81
+ modal.setAttribute('aria-labelledby', title.id);
82
+ }
83
+
84
+ // Close button handlers
85
+ closeButtons.forEach(button => {
86
+ const closeHandler = () => {
87
+ this.close(modal);
88
+ };
89
+ button.addEventListener('click', closeHandler);
90
+ cleanupFunctions.push(() => button.removeEventListener('click', closeHandler));
91
+ });
92
+
93
+ // Backdrop click handler
94
+ const backdropClickHandler = (e) => {
95
+ if (e.target === backdrop && modal.dataset.backdrop !== 'static') {
96
+ this.close(modal);
97
+ }
98
+ };
99
+ backdrop.addEventListener('click', backdropClickHandler);
100
+ cleanupFunctions.push(() => backdrop.removeEventListener('click', backdropClickHandler));
101
+
102
+ // ESC key handler
103
+ const escKeyHandler = (e) => {
104
+ if (e.key === 'Escape' && this.openModals.length > 0) {
105
+ const topModal = this.openModals[this.openModals.length - 1];
106
+ if (topModal === modal && topModal.dataset.keyboard !== 'false') {
107
+ this.close(topModal);
108
+ }
109
+ }
110
+ };
111
+ document.addEventListener('keydown', escKeyHandler);
112
+ cleanupFunctions.push(() => document.removeEventListener('keydown', escKeyHandler));
113
+
114
+ this.modals.set(modal, { backdrop, dialog, trapHandler: null, cleanup: cleanupFunctions });
115
+ },
116
+
117
+ /**
118
+ * Create backdrop element
119
+ * @param {HTMLElement} modal - Modal element
120
+ * @returns {HTMLElement} Backdrop element
121
+ */
122
+ createBackdrop: function (modal) {
123
+ let backdrop = modal.querySelector('.vd-modal-backdrop');
124
+
125
+ if (!backdrop) {
126
+ backdrop = document.createElement('div');
127
+ backdrop.className = 'vd-modal-backdrop';
128
+ document.body.appendChild(backdrop);
129
+ }
130
+
131
+ return backdrop;
132
+ },
133
+
134
+ /**
135
+ * Open modal
136
+ * @param {HTMLElement|string} modal - Modal element or selector
137
+ */
138
+ open: function (modal) {
139
+ const el = typeof modal === 'string' ? document.querySelector(modal) : modal;
140
+
141
+ if (!el) {
142
+ console.warn('[Vanduo Modals] Modal element not found:', modal);
143
+ return;
144
+ }
145
+
146
+ if (!this.modals.has(el)) {
147
+ console.warn('[Vanduo Modals] Modal not initialized:', el);
148
+ return;
149
+ }
150
+
151
+ const modalData = this.modals.get(el);
152
+ const { backdrop, dialog: _dialog } = modalData;
153
+
154
+ // Increment z-index for stacking
155
+ this.zIndexCounter += 10;
156
+ el.style.zIndex = this.zIndexCounter;
157
+ backdrop.style.zIndex = this.zIndexCounter - 1;
158
+
159
+ // Add to open modals stack
160
+ this.openModals.push(el);
161
+
162
+ // Show backdrop
163
+ backdrop.classList.add('is-visible');
164
+
165
+ // Show modal
166
+ el.classList.add('is-open');
167
+ el.setAttribute('aria-hidden', 'false');
168
+
169
+ // Lock body scroll
170
+ if (this.openModals.length === 1) {
171
+ document.body.classList.add('body-modal-open');
172
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
173
+ if (scrollbarWidth > 0) {
174
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
175
+ }
176
+ }
177
+
178
+ // Focus trap (store handler for cleanup)
179
+ const trapHandler = this.trapFocus(el);
180
+ modalData.trapHandler = trapHandler;
181
+
182
+ // Auto-focus first focusable element
183
+ setTimeout(() => {
184
+ const firstFocusable = this.getFocusableElements(el)[0];
185
+ if (firstFocusable) {
186
+ firstFocusable.focus();
187
+ }
188
+ }, 100);
189
+
190
+ // Dispatch event
191
+ el.dispatchEvent(new CustomEvent('modal:open', { bubbles: true }));
192
+ },
193
+
194
+ /**
195
+ * Close modal
196
+ * @param {HTMLElement|string} modal - Modal element or selector
197
+ */
198
+ close: function (modal) {
199
+ const el = typeof modal === 'string' ? document.querySelector(modal) : modal;
200
+
201
+ if (!el) {
202
+ console.warn('[Vanduo Modals] Modal element not found:', modal);
203
+ return;
204
+ }
205
+
206
+ if (!this.modals.has(el)) {
207
+ console.warn('[Vanduo Modals] Modal not initialized:', el);
208
+ return;
209
+ }
210
+
211
+ const modalData = this.modals.get(el);
212
+ const { backdrop, trapHandler } = modalData;
213
+
214
+ // Remove focus trap event listener to prevent memory leak
215
+ if (trapHandler) {
216
+ el.removeEventListener('keydown', trapHandler);
217
+ modalData.trapHandler = null;
218
+ }
219
+
220
+ // Remove from open modals stack
221
+ const index = this.openModals.indexOf(el);
222
+ if (index > -1) {
223
+ this.openModals.splice(index, 1);
224
+ }
225
+
226
+ // Hide modal
227
+ el.classList.remove('is-open');
228
+ el.setAttribute('aria-hidden', 'true');
229
+
230
+ // Hide backdrop if no other modals open
231
+ if (this.openModals.length === 0) {
232
+ backdrop.classList.remove('is-visible');
233
+ document.body.classList.remove('body-modal-open');
234
+ document.body.style.paddingRight = '';
235
+ // Reset z-index counter to prevent indefinite growth
236
+ this.zIndexCounter = 1050;
237
+ } else {
238
+ // Show backdrop for top modal
239
+ const topModal = this.openModals[this.openModals.length - 1];
240
+ const topBackdrop = this.modals.get(topModal).backdrop;
241
+ topBackdrop.classList.add('is-visible');
242
+ }
243
+
244
+ // Return focus to trigger
245
+ const trigger = document.querySelector(`[data-modal="#${el.id}"]`);
246
+ if (trigger) {
247
+ trigger.focus();
248
+ }
249
+
250
+ // Dispatch event
251
+ el.dispatchEvent(new CustomEvent('modal:close', { bubbles: true }));
252
+ },
253
+
254
+ /**
255
+ * Trap focus within modal
256
+ * @param {HTMLElement} modal - Modal element
257
+ * @returns {Function} The trap handler function for cleanup
258
+ */
259
+ trapFocus: function (modal) {
260
+ const self = this;
261
+
262
+ const trapHandler = function (e) {
263
+ if (e.key !== 'Tab') {
264
+ return;
265
+ }
266
+
267
+ const focusableElements = self.getFocusableElements(modal);
268
+ const firstElement = focusableElements[0];
269
+ const lastElement = focusableElements[focusableElements.length - 1];
270
+
271
+ if (e.shiftKey) {
272
+ // Shift + Tab
273
+ if (document.activeElement === firstElement) {
274
+ e.preventDefault();
275
+ lastElement.focus();
276
+ }
277
+ } else {
278
+ // Tab
279
+ if (document.activeElement === lastElement) {
280
+ e.preventDefault();
281
+ firstElement.focus();
282
+ }
283
+ }
284
+ };
285
+
286
+ modal.addEventListener('keydown', trapHandler);
287
+ return trapHandler;
288
+ },
289
+
290
+ /**
291
+ * Get focusable elements within modal
292
+ * @param {HTMLElement} modal - Modal element
293
+ * @returns {Array<HTMLElement>} Focusable elements
294
+ */
295
+ getFocusableElements: function (modal) {
296
+ const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
297
+ return Array.from(modal.querySelectorAll(selector)).filter(el => {
298
+ return !el.hasAttribute('disabled') &&
299
+ el.offsetWidth > 0 &&
300
+ el.offsetHeight > 0;
301
+ });
302
+ },
303
+
304
+ /**
305
+ * Toggle modal
306
+ * @param {HTMLElement|string} modal - Modal element or selector
307
+ */
308
+ toggle: function (modal) {
309
+ const el = typeof modal === 'string' ? document.querySelector(modal) : modal;
310
+ if (el) {
311
+ if (el.classList.contains('is-open')) {
312
+ this.close(el);
313
+ } else {
314
+ this.open(el);
315
+ }
316
+ }
317
+ },
318
+
319
+ /**
320
+ * Destroy a modal instance and clean up event listeners
321
+ * @param {HTMLElement} modal - Modal element
322
+ */
323
+ destroy: function (modal) {
324
+ const modalData = this.modals.get(modal);
325
+ if (!modalData) return;
326
+
327
+ // Close if open
328
+ if (modal.classList.contains('is-open')) {
329
+ this.close(modal);
330
+ }
331
+
332
+ // Run all cleanup functions
333
+ if (modalData.cleanup) {
334
+ modalData.cleanup.forEach(fn => fn());
335
+ }
336
+
337
+ // Remove created backdrop
338
+ if (modalData.backdrop && modalData.backdrop.parentNode) {
339
+ modalData.backdrop.parentNode.removeChild(modalData.backdrop);
340
+ }
341
+
342
+ this.modals.delete(modal);
343
+ },
344
+
345
+ /**
346
+ * Destroy all modal instances
347
+ */
348
+ destroyAll: function () {
349
+ this.modals.forEach((data, modal) => {
350
+ this.destroy(modal);
351
+ });
352
+ // Clean up trigger listeners
353
+ this._triggerCleanups.forEach(fn => fn());
354
+ this._triggerCleanups = [];
355
+ }
356
+ };
357
+
358
+ // Register with Vanduo framework if available
359
+ if (typeof window.Vanduo !== 'undefined') {
360
+ window.Vanduo.register('modals', Modals);
361
+ }
362
+
363
+ // Expose globally
364
+ window.VanduoModals = Modals;
365
+
366
+ })();
367
+
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Vanduo Framework - Navbar Component
3
+ * JavaScript functionality for navbar mobile menu
4
+ */
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Navbar Component
11
+ */
12
+ const Navbar = {
13
+ // Store initialized navbars and their cleanup functions
14
+ instances: new Map(),
15
+
16
+ /**
17
+ * Get the breakpoint value from CSS variable or use fallback
18
+ * @returns {number} Breakpoint in pixels
19
+ */
20
+ getBreakpoint: function () {
21
+ const root = getComputedStyle(document.documentElement);
22
+ const breakpointValue = root.getPropertyValue('--breakpoint-lg').trim();
23
+
24
+ // Parse the value (could be "992px" or just "992")
25
+ const parsed = parseInt(breakpointValue, 10);
26
+ return isNaN(parsed) ? 992 : parsed;
27
+ },
28
+
29
+ /**
30
+ * Initialize navbar component
31
+ */
32
+ init: function () {
33
+ const navbars = document.querySelectorAll('.vd-navbar');
34
+
35
+ navbars.forEach(navbar => {
36
+ // Skip if already initialized
37
+ if (this.instances.has(navbar)) {
38
+ return;
39
+ }
40
+ this.initNavbar(navbar);
41
+ });
42
+ },
43
+
44
+ /**
45
+ * Initialize a single navbar
46
+ * @param {HTMLElement} navbar - Navbar element
47
+ */
48
+ initNavbar: function (navbar) {
49
+ const toggle = navbar.querySelector('.vd-navbar-toggle, .vd-navbar-burger');
50
+ const menu = navbar.querySelector('.vd-navbar-menu');
51
+ const overlay = navbar.querySelector('.vd-navbar-overlay') || this.createOverlay(navbar);
52
+
53
+ if (!toggle || !menu) {
54
+ return;
55
+ }
56
+
57
+ // Store cleanup functions for this navbar instance
58
+ const cleanupFunctions = [];
59
+
60
+ // Toggle menu on button click
61
+ const toggleClickHandler = (e) => {
62
+ e.preventDefault();
63
+ e.stopPropagation();
64
+ this.toggleMenu(navbar, toggle, menu, overlay);
65
+ };
66
+ toggle.addEventListener('click', toggleClickHandler);
67
+ cleanupFunctions.push(() => toggle.removeEventListener('click', toggleClickHandler));
68
+
69
+ // Close menu on overlay click
70
+ if (overlay) {
71
+ const overlayClickHandler = () => {
72
+ this.closeMenu(navbar, toggle, menu, overlay);
73
+ };
74
+ overlay.addEventListener('click', overlayClickHandler);
75
+ cleanupFunctions.push(() => overlay.removeEventListener('click', overlayClickHandler));
76
+ }
77
+
78
+ // Close menu on escape key
79
+ const keydownHandler = (e) => {
80
+ if (e.key === 'Escape' && menu.classList.contains('is-open')) {
81
+ this.closeMenu(navbar, toggle, menu, overlay);
82
+ }
83
+ };
84
+ document.addEventListener('keydown', keydownHandler);
85
+ cleanupFunctions.push(() => document.removeEventListener('keydown', keydownHandler));
86
+
87
+ // Close menu on window resize (if resizing to desktop)
88
+ let resizeTimer;
89
+ const resizeHandler = () => {
90
+ clearTimeout(resizeTimer);
91
+ resizeTimer = setTimeout(() => {
92
+ const breakpoint = this.getBreakpoint();
93
+ if (window.innerWidth >= breakpoint && menu.classList.contains('is-open')) {
94
+ this.closeMenu(navbar, toggle, menu, overlay);
95
+ }
96
+ }, 250);
97
+ };
98
+ window.addEventListener('resize', resizeHandler);
99
+ cleanupFunctions.push(() => {
100
+ clearTimeout(resizeTimer);
101
+ window.removeEventListener('resize', resizeHandler);
102
+ });
103
+
104
+ // Close menu when clicking outside
105
+ const documentClickHandler = (e) => {
106
+ if (menu.classList.contains('is-open') &&
107
+ !navbar.contains(e.target) &&
108
+ !menu.contains(e.target)) {
109
+ this.closeMenu(navbar, toggle, menu, overlay);
110
+ }
111
+ };
112
+ document.addEventListener('click', documentClickHandler);
113
+ cleanupFunctions.push(() => document.removeEventListener('click', documentClickHandler));
114
+
115
+ // Handle dropdown toggles in mobile menu
116
+ const dropdownToggles = menu.querySelectorAll('.vd-navbar-dropdown > .vd-nav-link, .vd-navbar-dropdown > .nav-link');
117
+ dropdownToggles.forEach(dropdownToggle => {
118
+ const dropdownClickHandler = (e) => {
119
+ const breakpoint = this.getBreakpoint();
120
+ if (window.innerWidth < breakpoint) {
121
+ e.preventDefault();
122
+ const dropdown = dropdownToggle.parentElement;
123
+ const dropdownMenu = dropdown.querySelector('.vd-navbar-dropdown-menu');
124
+
125
+ if (dropdownMenu) {
126
+ dropdownMenu.classList.toggle('is-open');
127
+ }
128
+ }
129
+ };
130
+ dropdownToggle.addEventListener('click', dropdownClickHandler);
131
+ cleanupFunctions.push(() => dropdownToggle.removeEventListener('click', dropdownClickHandler));
132
+ });
133
+
134
+ // Store instance with cleanup functions
135
+ this.instances.set(navbar, {
136
+ toggle,
137
+ menu,
138
+ overlay,
139
+ cleanup: cleanupFunctions
140
+ });
141
+ },
142
+
143
+ /**
144
+ * Destroy a navbar instance and clean up event listeners
145
+ * @param {HTMLElement} navbar - Navbar element
146
+ */
147
+ destroy: function (navbar) {
148
+ const instance = this.instances.get(navbar);
149
+ if (!instance) {
150
+ return;
151
+ }
152
+
153
+ // Run all cleanup functions
154
+ instance.cleanup.forEach(fn => fn());
155
+
156
+ // Remove created overlay if it exists
157
+ if (instance.overlay && instance.overlay.parentNode) {
158
+ instance.overlay.parentNode.removeChild(instance.overlay);
159
+ }
160
+
161
+ // Remove from instances map
162
+ this.instances.delete(navbar);
163
+ },
164
+
165
+ /**
166
+ * Destroy all navbar instances
167
+ */
168
+ destroyAll: function () {
169
+ this.instances.forEach((instance, navbar) => {
170
+ this.destroy(navbar);
171
+ });
172
+ },
173
+
174
+ /**
175
+ * Toggle mobile menu
176
+ * @param {HTMLElement} navbar - Navbar element
177
+ * @param {HTMLElement} toggle - Toggle button
178
+ * @param {HTMLElement} menu - Menu element
179
+ * @param {HTMLElement} overlay - Overlay element
180
+ */
181
+ toggleMenu: function (navbar, toggle, menu, overlay) {
182
+ const isOpen = menu.classList.contains('is-open');
183
+
184
+ if (isOpen) {
185
+ this.closeMenu(navbar, toggle, menu, overlay);
186
+ } else {
187
+ this.openMenu(navbar, toggle, menu, overlay);
188
+ }
189
+ },
190
+
191
+ /**
192
+ * Open mobile menu
193
+ * @param {HTMLElement} navbar - Navbar element
194
+ * @param {HTMLElement} toggle - Toggle button
195
+ * @param {HTMLElement} menu - Menu element
196
+ * @param {HTMLElement} overlay - Overlay element
197
+ */
198
+ openMenu: function (navbar, toggle, menu, overlay) {
199
+ menu.classList.add('is-open');
200
+ toggle.classList.add('is-active');
201
+
202
+ if (overlay) {
203
+ overlay.classList.add('is-active');
204
+ }
205
+
206
+ // Prevent body scroll when menu is open
207
+ document.body.style.overflow = 'hidden';
208
+
209
+ // Set ARIA attributes
210
+ toggle.setAttribute('aria-expanded', 'true');
211
+ menu.setAttribute('aria-hidden', 'false');
212
+ },
213
+
214
+ /**
215
+ * Close mobile menu
216
+ * @param {HTMLElement} navbar - Navbar element
217
+ * @param {HTMLElement} toggle - Toggle button
218
+ * @param {HTMLElement} menu - Menu element
219
+ * @param {HTMLElement} overlay - Overlay element
220
+ */
221
+ closeMenu: function (navbar, toggle, menu, overlay) {
222
+ menu.classList.remove('is-open');
223
+ toggle.classList.remove('is-active');
224
+
225
+ if (overlay) {
226
+ overlay.classList.remove('is-active');
227
+ }
228
+
229
+ // Restore body scroll
230
+ document.body.style.overflow = '';
231
+
232
+ // Close all dropdown menus
233
+ const dropdownMenus = menu.querySelectorAll('.vd-navbar-dropdown-menu.is-open');
234
+ dropdownMenus.forEach(dropdownMenu => {
235
+ dropdownMenu.classList.remove('is-open');
236
+ });
237
+
238
+ // Set ARIA attributes
239
+ toggle.setAttribute('aria-expanded', 'false');
240
+ menu.setAttribute('aria-hidden', 'true');
241
+ },
242
+
243
+ /**
244
+ * Create overlay element if it doesn't exist
245
+ * @param {HTMLElement} navbar - Navbar element
246
+ * @returns {HTMLElement} Overlay element
247
+ */
248
+ createOverlay: function (_navbar) {
249
+ const overlay = document.createElement('div');
250
+ overlay.className = 'vd-navbar-overlay';
251
+ document.body.appendChild(overlay);
252
+ return overlay;
253
+ }
254
+ };
255
+
256
+ // Register with Vanduo framework if available
257
+ if (typeof window.Vanduo !== 'undefined') {
258
+ window.Vanduo.register('navbar', Navbar);
259
+ }
260
+
261
+ // Expose globally
262
+ window.VanduoNavbar = Navbar;
263
+
264
+ })();