@vanduo-oss/framework 1.2.3

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 (197) 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 +6178 -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 +60950 -0
  109. package/dist/vanduo.css.map +1 -0
  110. package/dist/vanduo.esm.js +6157 -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 +6154 -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 +641 -0
  173. package/js/components/collapsible.js +226 -0
  174. package/js/components/doc-search.js +953 -0
  175. package/js/components/draggable.js +728 -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/lazy-load.js +353 -0
  181. package/js/components/modals.js +367 -0
  182. package/js/components/navbar.js +264 -0
  183. package/js/components/pagination.js +286 -0
  184. package/js/components/parallax.js +216 -0
  185. package/js/components/preloader.js +183 -0
  186. package/js/components/select.js +444 -0
  187. package/js/components/sidenav.js +303 -0
  188. package/js/components/tabs.js +303 -0
  189. package/js/components/theme-customizer.js +800 -0
  190. package/js/components/theme-switcher.js +183 -0
  191. package/js/components/toast.js +343 -0
  192. package/js/components/tooltips.js +306 -0
  193. package/js/index.js +53 -0
  194. package/js/utils/helpers.js +318 -0
  195. package/js/utils/lifecycle.js +135 -0
  196. package/js/vanduo.js +120 -0
  197. package/package.json +78 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Vanduo Framework - Tooltips Component
3
+ * JavaScript functionality for tooltips
4
+ */
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Tooltips Component
11
+ */
12
+ const Tooltips = {
13
+ tooltips: new Map(),
14
+ delayTimers: new Map(),
15
+
16
+ /**
17
+ * Sanitize HTML — delegates to shared sanitizeHtml from helpers.js
18
+ * @param {string} input
19
+ * @returns {string} sanitized HTML
20
+ */
21
+ sanitizeHtml: function (input) {
22
+ if (typeof sanitizeHtml === 'function') {
23
+ return sanitizeHtml(input);
24
+ }
25
+ // Fallback: strip all HTML
26
+ const div = document.createElement('div');
27
+ div.textContent = input || '';
28
+ return div.innerHTML;
29
+ },
30
+
31
+ /**
32
+ * Initialize tooltips
33
+ */
34
+ init: function () {
35
+ const elements = document.querySelectorAll('[data-tooltip], [data-tooltip-html]');
36
+
37
+ elements.forEach(element => {
38
+ if (this.tooltips.has(element)) {
39
+ return;
40
+ }
41
+ this.initTooltip(element);
42
+ });
43
+ },
44
+
45
+ /**
46
+ * Initialize a single tooltip
47
+ * @param {HTMLElement} element - Element with tooltip
48
+ */
49
+ initTooltip: function (element) {
50
+ const tooltip = this.createTooltip(element);
51
+ const cleanupFunctions = [];
52
+
53
+ // Show on hover/focus
54
+ const enterHandler = () => { this.showTooltip(element, tooltip); };
55
+ const leaveHandler = () => { this.hideTooltip(element, tooltip); };
56
+ const focusHandler = () => { this.showTooltip(element, tooltip); };
57
+ const blurHandler = () => { this.hideTooltip(element, tooltip); };
58
+
59
+ element.addEventListener('mouseenter', enterHandler);
60
+ element.addEventListener('mouseleave', leaveHandler);
61
+ element.addEventListener('focus', focusHandler);
62
+ element.addEventListener('blur', blurHandler);
63
+
64
+ cleanupFunctions.push(
65
+ () => element.removeEventListener('mouseenter', enterHandler),
66
+ () => element.removeEventListener('mouseleave', leaveHandler),
67
+ () => element.removeEventListener('focus', focusHandler),
68
+ () => element.removeEventListener('blur', blurHandler)
69
+ );
70
+
71
+ this.tooltips.set(element, { tooltip, cleanup: cleanupFunctions });
72
+ },
73
+
74
+ /**
75
+ * Create tooltip element
76
+ * @param {HTMLElement} element - Target element
77
+ * @returns {HTMLElement} Tooltip element
78
+ */
79
+ createTooltip: function (element) {
80
+ const tooltip = document.createElement('div');
81
+ tooltip.className = 'vd-tooltip';
82
+ tooltip.setAttribute('role', 'tooltip');
83
+ tooltip.setAttribute('aria-hidden', 'true');
84
+
85
+ // Generate unique ID and link via aria-describedby
86
+ const tooltipId = 'tooltip-' + Math.random().toString(36).substr(2, 9);
87
+ tooltip.id = tooltipId;
88
+ element.setAttribute('aria-describedby', tooltipId);
89
+
90
+ // Get content
91
+ const htmlContent = element.dataset.tooltipHtml;
92
+ const textContent = element.dataset.tooltip;
93
+
94
+ if (htmlContent) {
95
+ tooltip.innerHTML = this.sanitizeHtml(htmlContent);
96
+ tooltip.classList.add('vd-tooltip-html');
97
+ } else if (textContent) {
98
+ tooltip.textContent = textContent;
99
+ }
100
+
101
+ // Get placement
102
+ const placement = element.dataset.tooltipPlacement || element.dataset.placement || 'top';
103
+ tooltip.setAttribute('data-placement', placement);
104
+ tooltip.classList.add(`vd-tooltip-${placement}`);
105
+
106
+ // Get variant
107
+ if (element.dataset.tooltipVariant) {
108
+ tooltip.classList.add(`vd-tooltip-${element.dataset.tooltipVariant}`);
109
+ }
110
+
111
+ // Get size
112
+ if (element.dataset.tooltipSize) {
113
+ tooltip.classList.add(`vd-tooltip-${element.dataset.tooltipSize}`);
114
+ }
115
+
116
+ // Get delay
117
+ const delay = parseInt(element.dataset.tooltipDelay) || 0;
118
+ tooltip.dataset.delay = delay;
119
+
120
+ document.body.appendChild(tooltip);
121
+
122
+ return tooltip;
123
+ },
124
+
125
+ /**
126
+ * Show tooltip
127
+ * @param {HTMLElement} element - Target element
128
+ * @param {HTMLElement} tooltip - Tooltip element
129
+ */
130
+ showTooltip: function (element, tooltip) {
131
+ const delay = parseInt(tooltip.dataset.delay) || 0;
132
+
133
+ if (delay > 0) {
134
+ const timer = setTimeout(() => {
135
+ this.positionTooltip(element, tooltip);
136
+ tooltip.classList.add('is-visible');
137
+ tooltip.setAttribute('aria-hidden', 'false');
138
+ }, delay);
139
+ this.delayTimers.set(element, timer);
140
+ } else {
141
+ this.positionTooltip(element, tooltip);
142
+ tooltip.classList.add('is-visible');
143
+ tooltip.setAttribute('aria-hidden', 'false');
144
+ }
145
+ },
146
+
147
+ /**
148
+ * Hide tooltip
149
+ * @param {HTMLElement} element - Target element
150
+ * @param {HTMLElement} tooltip - Tooltip element
151
+ */
152
+ hideTooltip: function (element, tooltip) {
153
+ // Clear delay timer if exists
154
+ const timer = this.delayTimers.get(element);
155
+ if (timer) {
156
+ clearTimeout(timer);
157
+ this.delayTimers.delete(element);
158
+ }
159
+
160
+ tooltip.classList.remove('is-visible');
161
+ tooltip.setAttribute('aria-hidden', 'true');
162
+ },
163
+
164
+ /**
165
+ * Position tooltip relative to element
166
+ * @param {HTMLElement} element - Target element
167
+ * @param {HTMLElement} tooltip - Tooltip element
168
+ */
169
+ positionTooltip: function (element, tooltip) {
170
+ const placement = tooltip.dataset.placement || 'top';
171
+ const rect = element.getBoundingClientRect();
172
+ const tooltipRect = tooltip.getBoundingClientRect();
173
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
174
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
175
+
176
+ let top = 0;
177
+ let left = 0;
178
+
179
+ switch (placement) {
180
+ case 'top':
181
+ top = rect.top + scrollTop - tooltipRect.height - 8;
182
+ left = rect.left + scrollLeft + (rect.width / 2) - (tooltipRect.width / 2);
183
+ break;
184
+ case 'bottom':
185
+ top = rect.bottom + scrollTop + 8;
186
+ left = rect.left + scrollLeft + (rect.width / 2) - (tooltipRect.width / 2);
187
+ break;
188
+ case 'left':
189
+ top = rect.top + scrollTop + (rect.height / 2) - (tooltipRect.height / 2);
190
+ left = rect.left + scrollLeft - tooltipRect.width - 8;
191
+ break;
192
+ case 'right':
193
+ top = rect.top + scrollTop + (rect.height / 2) - (tooltipRect.height / 2);
194
+ left = rect.right + scrollLeft + 8;
195
+ break;
196
+ }
197
+
198
+ // Prevent overflow
199
+ const viewportWidth = window.innerWidth;
200
+ const viewportHeight = window.innerHeight;
201
+ const padding = 8;
202
+
203
+ if (left < padding) {
204
+ left = padding;
205
+ } else if (left + tooltipRect.width > viewportWidth - padding) {
206
+ left = viewportWidth - tooltipRect.width - padding;
207
+ }
208
+
209
+ if (top < scrollTop + padding) {
210
+ top = scrollTop + padding;
211
+ } else if (top + tooltipRect.height > scrollTop + viewportHeight - padding) {
212
+ top = scrollTop + viewportHeight - tooltipRect.height - padding;
213
+ }
214
+
215
+ // Use single style assignment with transform for better performance
216
+ tooltip.style.cssText = `position: absolute; top: 0; left: 0; transform: translate(${left}px, ${top}px);`;
217
+ },
218
+
219
+ /**
220
+ * Show tooltip programmatically
221
+ * @param {HTMLElement|string} element - Target element or selector
222
+ */
223
+ show: function (element) {
224
+ const el = typeof element === 'string' ? document.querySelector(element) : element;
225
+ if (el && this.tooltips.has(el)) {
226
+ const { tooltip } = this.tooltips.get(el);
227
+ this.showTooltip(el, tooltip);
228
+ }
229
+ },
230
+
231
+ /**
232
+ * Hide tooltip programmatically
233
+ * @param {HTMLElement|string} element - Target element or selector
234
+ */
235
+ hide: function (element) {
236
+ const el = typeof element === 'string' ? document.querySelector(element) : element;
237
+ if (el && this.tooltips.has(el)) {
238
+ const { tooltip } = this.tooltips.get(el);
239
+ this.hideTooltip(el, tooltip);
240
+ }
241
+ },
242
+
243
+ /**
244
+ * Update tooltip content
245
+ * @param {HTMLElement|string} element - Target element or selector
246
+ * @param {string} content - New content
247
+ * @param {boolean} isHtml - Whether content is HTML
248
+ */
249
+ update: function (element, content, isHtml = false) {
250
+ const el = typeof element === 'string' ? document.querySelector(element) : element;
251
+ if (el && this.tooltips.has(el)) {
252
+ const { tooltip } = this.tooltips.get(el);
253
+ if (isHtml) {
254
+ tooltip.innerHTML = this.sanitizeHtml(content);
255
+ tooltip.classList.add('vd-tooltip-html');
256
+ } else {
257
+ tooltip.textContent = content;
258
+ tooltip.classList.remove('vd-tooltip-html');
259
+ }
260
+ }
261
+ },
262
+
263
+ /**
264
+ * Destroy a tooltip instance and clean up
265
+ * @param {HTMLElement} element - Element with tooltip
266
+ */
267
+ destroy: function (element) {
268
+ const data = this.tooltips.get(element);
269
+ if (!data) return;
270
+
271
+ // Clear any pending timer
272
+ const timer = this.delayTimers.get(element);
273
+ if (timer) {
274
+ clearTimeout(timer);
275
+ this.delayTimers.delete(element);
276
+ }
277
+
278
+ data.cleanup.forEach(fn => fn());
279
+
280
+ // Remove tooltip element from DOM
281
+ if (data.tooltip && data.tooltip.parentNode) {
282
+ data.tooltip.parentNode.removeChild(data.tooltip);
283
+ }
284
+
285
+ this.tooltips.delete(element);
286
+ },
287
+
288
+ /**
289
+ * Destroy all tooltip instances
290
+ */
291
+ destroyAll: function () {
292
+ this.tooltips.forEach((data, element) => {
293
+ this.destroy(element);
294
+ });
295
+ }
296
+ };
297
+
298
+ // Register with Vanduo framework if available
299
+ if (typeof window.Vanduo !== 'undefined') {
300
+ window.Vanduo.register('tooltips', Tooltips);
301
+ }
302
+
303
+ // Expose globally
304
+ window.VanduoTooltips = Tooltips;
305
+
306
+ })();
package/js/index.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Vanduo Framework - Bundle Entry Point
3
+ * This file imports all framework components for bundling.
4
+ *
5
+ * All component files are side-effect modules that:
6
+ * 1. Define their component object
7
+ * 2. Register with window.Vanduo via Vanduo.register()
8
+ * 3. Expose a convenience global (e.g. window.VanduoTooltips)
9
+ *
10
+ * The IIFE build uses `globalName: 'VanduoBundle'` so that esbuild's
11
+ * wrapper variable does NOT shadow the real `window.Vanduo` that the
12
+ * side-effect scripts create. After the bundle executes, `window.Vanduo`
13
+ * is the fully-populated framework object.
14
+ *
15
+ * For ESM/CJS consumers we re-export `window.Vanduo` as the default
16
+ * and named export so `import { Vanduo }` and `const { Vanduo } = require()`
17
+ * both work.
18
+ */
19
+
20
+ // Utilities (must load first — helpers defines `ready()`, `safeStorageGet()` etc.)
21
+ import './utils/helpers.js';
22
+ import './utils/lifecycle.js';
23
+
24
+ // Core framework object (creates window.Vanduo)
25
+ import './vanduo.js';
26
+
27
+ // Components (each registers itself with window.Vanduo)
28
+ import './components/code-snippet.js';
29
+ import './components/collapsible.js';
30
+ import './components/dropdown.js';
31
+ import './components/font-switcher.js';
32
+ import './components/grid.js';
33
+ import './components/image-box.js';
34
+ import './components/modals.js';
35
+ import './components/navbar.js';
36
+ import './components/pagination.js';
37
+ import './components/parallax.js';
38
+ import './components/preloader.js';
39
+ import './components/select.js';
40
+ import './components/sidenav.js';
41
+ import './components/tabs.js';
42
+ import './components/theme-customizer.js';
43
+ import './components/theme-switcher.js';
44
+ import './components/toast.js';
45
+ import './components/tooltips.js';
46
+ import './components/doc-search.js';
47
+ import './components/draggable.js';
48
+ import './components/lazy-load.js';
49
+
50
+ // Re-export for ESM / CJS consumers
51
+ const Vanduo = window.Vanduo;
52
+ export { Vanduo };
53
+ export default Vanduo;
@@ -0,0 +1,318 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Vanduo Framework - Utility Helpers
5
+ * Common utility functions used across the framework
6
+ */
7
+
8
+ /**
9
+ * Check if element exists
10
+ * @param {string|HTMLElement} selector - CSS selector or element
11
+ * @returns {HTMLElement|null}
12
+ */
13
+ function $(selector) {
14
+ if (typeof selector === 'string') {
15
+ return document.querySelector(selector);
16
+ }
17
+ return selector;
18
+ }
19
+
20
+ /**
21
+ * Get all elements matching selector
22
+ * @param {string} selector - CSS selector
23
+ * @returns {NodeList}
24
+ */
25
+ function $$(selector) {
26
+ return document.querySelectorAll(selector);
27
+ }
28
+
29
+ /**
30
+ * Wait for DOM to be ready
31
+ * @param {Function} callback - Function to execute when DOM is ready
32
+ */
33
+ function ready(callback) {
34
+ if (document.readyState === 'loading') {
35
+ document.addEventListener('DOMContentLoaded', callback);
36
+ } else {
37
+ callback();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Safely get a value from localStorage
43
+ * @param {string} key - Storage key
44
+ * @param {string|null} fallback - Fallback when storage is unavailable
45
+ * @returns {string|null}
46
+ */
47
+ function safeStorageGet(key, fallback = null) {
48
+ try {
49
+ const value = localStorage.getItem(key);
50
+ return value !== null ? value : fallback;
51
+ } catch (_e) {
52
+ return fallback;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Safely set a value in localStorage
58
+ * @param {string} key - Storage key
59
+ * @param {string} value - Value to store
60
+ * @returns {boolean}
61
+ */
62
+ function safeStorageSet(key, value) {
63
+ try {
64
+ localStorage.setItem(key, value);
65
+ return true;
66
+ } catch (_e) {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Add event listener with delegation support
73
+ * @param {string|HTMLElement} target - Target element or selector
74
+ * @param {string} event - Event type
75
+ * @param {string|Function} handlerOrSelector - Event handler or selector for delegation
76
+ * @param {Function} handler - Event handler (if using delegation)
77
+ */
78
+ function on(target, event, handlerOrSelector, handler) {
79
+ const element = typeof target === 'string' ? $(target) : target;
80
+
81
+ if (!element) return;
82
+
83
+ if (typeof handlerOrSelector === 'function') {
84
+ // Direct event binding
85
+ element.addEventListener(event, handlerOrSelector);
86
+ } else {
87
+ // Event delegation
88
+ element.addEventListener(event, function (e) {
89
+ const delegateTarget = e.target.closest(handlerOrSelector);
90
+ if (delegateTarget && element.contains(delegateTarget)) {
91
+ try {
92
+ handler.call(delegateTarget, e);
93
+ } catch (error) {
94
+ console.warn('[Vanduo Helpers] Delegated handler error:', error);
95
+ }
96
+ }
97
+ });
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Remove event listener
103
+ * @param {string|HTMLElement} target - Target element or selector
104
+ * @param {string} event - Event type
105
+ * @param {Function} handler - Event handler
106
+ */
107
+ function off(target, event, handler) {
108
+ const element = typeof target === 'string' ? $(target) : target;
109
+ if (element) {
110
+ element.removeEventListener(event, handler);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Toggle class on element
116
+ * @param {string|HTMLElement} selector - CSS selector or element
117
+ * @param {string} className - Class name to toggle
118
+ */
119
+ function toggleClass(selector, className) {
120
+ const element = typeof selector === 'string' ? $(selector) : selector;
121
+ if (element) {
122
+ element.classList.toggle(className);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Add class to element
128
+ * @param {string|HTMLElement} selector - CSS selector or element
129
+ * @param {string} className - Class name to add
130
+ */
131
+ function addClass(selector, className) {
132
+ const element = typeof selector === 'string' ? $(selector) : selector;
133
+ if (element) {
134
+ element.classList.add(className);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Remove class from element
140
+ * @param {string|HTMLElement} selector - CSS selector or element
141
+ * @param {string} className - Class name to remove
142
+ */
143
+ function removeClass(selector, className) {
144
+ const element = typeof selector === 'string' ? $(selector) : selector;
145
+ if (element) {
146
+ element.classList.remove(className);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Check if element has class
152
+ * @param {string|HTMLElement} selector - CSS selector or element
153
+ * @param {string} className - Class name to check
154
+ * @returns {boolean}
155
+ */
156
+ function hasClass(selector, className) {
157
+ const element = typeof selector === 'string' ? $(selector) : selector;
158
+ return element ? element.classList.contains(className) : false;
159
+ }
160
+
161
+ /**
162
+ * Get or set data attribute
163
+ * @param {HTMLElement} element - Element
164
+ * @param {string} name - Data attribute name (without data- prefix)
165
+ * @param {string} value - Value to set (optional)
166
+ * @returns {string|undefined}
167
+ */
168
+ function data(element, name, value) {
169
+ if (value !== undefined) {
170
+ element.setAttribute(`data-${name}`, value);
171
+ return value;
172
+ }
173
+ return element.getAttribute(`data-${name}`);
174
+ }
175
+
176
+ /**
177
+ * Debounce function
178
+ * @param {Function} func - Function to debounce
179
+ * @param {number} wait - Wait time in milliseconds
180
+ * @returns {Function}
181
+ */
182
+ function debounce(func, wait) {
183
+ let timeout;
184
+ return function executedFunction(...args) {
185
+ const later = () => {
186
+ clearTimeout(timeout);
187
+ func(...args);
188
+ };
189
+ clearTimeout(timeout);
190
+ timeout = setTimeout(later, wait);
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Throttle function
196
+ * @param {Function} func - Function to throttle
197
+ * @param {number} limit - Time limit in milliseconds
198
+ * @returns {Function}
199
+ */
200
+ function throttle(func, limit) {
201
+ let inThrottle;
202
+ return function (...args) {
203
+ if (!inThrottle) {
204
+ func.apply(this, args);
205
+ inThrottle = true;
206
+ setTimeout(() => inThrottle = false, limit);
207
+ }
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Check if element is visible
213
+ * @param {HTMLElement} element - Element to check
214
+ * @returns {boolean}
215
+ */
216
+ function isVisible(element) {
217
+ if (!element) return false;
218
+ const style = window.getComputedStyle(element);
219
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
220
+ }
221
+
222
+ /**
223
+ * Get element position relative to viewport
224
+ * @param {HTMLElement} element - Element
225
+ * @returns {Object} - Object with top, left, right, bottom, width, height
226
+ */
227
+ function getPosition(element) {
228
+ if (!element) return null;
229
+ const rect = element.getBoundingClientRect();
230
+ return {
231
+ top: rect.top,
232
+ left: rect.left,
233
+ right: rect.right,
234
+ bottom: rect.bottom,
235
+ width: rect.width,
236
+ height: rect.height
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Escape HTML special characters to prevent injection
242
+ * @param {string} str - String to escape
243
+ * @returns {string} Escaped string safe for insertion into HTML
244
+ */
245
+ function escapeHtml(str) {
246
+ if (!str) return '';
247
+ const div = document.createElement('div');
248
+ div.appendChild(document.createTextNode(str));
249
+ return div.innerHTML;
250
+ }
251
+
252
+ /**
253
+ * Basic HTML sanitizer (whitelist-based) — runs in the browser without external libs.
254
+ * Keeps a small set of tags and strips disallowed tags and attributes. Safe for
255
+ * simple rich text (use server-side or DOMPurify for stronger guarantees).
256
+ * @param {string} input
257
+ * @returns {string} sanitized HTML
258
+ */
259
+ function sanitizeHtml(input) {
260
+ if (!input) return '';
261
+ let doc;
262
+ try {
263
+ doc = new DOMParser().parseFromString(input, 'text/html');
264
+ } catch (_error) {
265
+ // Fail closed to plain escaped text if parser is unavailable/fails.
266
+ return escapeHtml(input);
267
+ }
268
+ const allowed = ['B', 'STRONG', 'I', 'EM', 'BR', 'A', 'SPAN', 'U', 'SVG', 'PATH', 'LINE', 'CIRCLE', 'POLYLINE', 'RECT', 'G'];
269
+
270
+ const sanitizeNode = function (node) {
271
+ const children = Array.from(node.childNodes);
272
+ children.forEach(function (child) {
273
+ if (child.nodeType === Node.TEXT_NODE) return;
274
+
275
+ if (!allowed.includes(child.nodeName)) {
276
+ const text = document.createTextNode(child.textContent);
277
+ node.replaceChild(text, child);
278
+ return;
279
+ }
280
+
281
+ if (child.nodeName === 'A') {
282
+ const href = child.getAttribute('href') || '';
283
+ try {
284
+ const url = new URL(href, location.href);
285
+ if (!['http:', 'https:', 'mailto:'].includes(url.protocol)) {
286
+ child.removeAttribute('href');
287
+ }
288
+ } catch (_e) {
289
+ child.removeAttribute('href');
290
+ }
291
+ child.removeAttribute('target');
292
+ child.removeAttribute('rel');
293
+ } else if (child.nodeName === 'SVG' || child.closest && child.closest('svg')) {
294
+ // Allow safe SVG presentation attributes only
295
+ const safeSvgAttrs = ['xmlns', 'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width',
296
+ 'stroke-linecap', 'stroke-linejoin', 'd', 'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2', 'points',
297
+ 'transform', 'class'];
298
+ const attrs = Array.from(child.attributes || []);
299
+ attrs.forEach(function (a) {
300
+ if (!safeSvgAttrs.includes(a.name)) {
301
+ child.removeAttribute(a.name);
302
+ }
303
+ });
304
+ } else {
305
+ const otherAttrs = Array.from(child.attributes || []);
306
+ otherAttrs.forEach(function (a) { child.removeAttribute(a.name); });
307
+ }
308
+
309
+ sanitizeNode(child);
310
+ });
311
+ };
312
+
313
+ sanitizeNode(doc.body);
314
+ return doc.body.innerHTML;
315
+ }
316
+
317
+
318
+