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.
- package/LICENSE +35 -0
- package/README.md +216 -0
- package/css/components/alerts.css +224 -0
- package/css/components/avatar.css +275 -0
- package/css/components/badges.css +230 -0
- package/css/components/breadcrumbs.css +146 -0
- package/css/components/button-group.css +82 -0
- package/css/components/buttons.css +530 -0
- package/css/components/cards.css +304 -0
- package/css/components/chips.css +259 -0
- package/css/components/code-snippet.css +555 -0
- package/css/components/collapsible.css +267 -0
- package/css/components/collections.css +253 -0
- package/css/components/doc-search.css +464 -0
- package/css/components/doc-tabs.css +38 -0
- package/css/components/draggable.css +317 -0
- package/css/components/dropdown.css +266 -0
- package/css/components/footer.css +375 -0
- package/css/components/forms.css +1774 -0
- package/css/components/image-box.css +279 -0
- package/css/components/modals.css +285 -0
- package/css/components/navbar.css +530 -0
- package/css/components/pagination.css +186 -0
- package/css/components/preloader.css +340 -0
- package/css/components/progress.css +107 -0
- package/css/components/sidenav.css +301 -0
- package/css/components/skeleton.css +241 -0
- package/css/components/spinner.css +144 -0
- package/css/components/tabs.css +327 -0
- package/css/components/theme-customizer.css +835 -0
- package/css/components/toast.css +357 -0
- package/css/components/tooltips.css +270 -0
- package/css/core/colors.css +1017 -0
- package/css/core/fonts.css +266 -0
- package/css/core/grid.css +1699 -0
- package/css/core/helpers.css +2202 -0
- package/css/core/reset.css +128 -0
- package/css/core/tokens.css +213 -0
- package/css/core/typography.css +405 -0
- package/css/core/vd-aliases.css +47 -0
- package/css/effects/parallax.css +113 -0
- package/css/icons/icons-all.css +23 -0
- package/css/icons/icons.css +25 -0
- package/css/utilities/media.css +167 -0
- package/css/utilities/print.css +111 -0
- package/css/utilities/shadow.css +243 -0
- package/css/utilities/table.css +381 -0
- package/css/utilities/transforms.css +71 -0
- package/css/utilities/transitions.css +87 -0
- package/css/vanduo.css +80 -0
- package/dist/build-info.json +6 -0
- package/dist/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
- package/dist/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
- package/dist/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
- package/dist/fonts/inter/inter-bold.woff2 +0 -0
- package/dist/fonts/inter/inter-medium.woff2 +0 -0
- package/dist/fonts/inter/inter-regular.woff2 +0 -0
- package/dist/fonts/inter/inter-semibold.woff2 +0 -0
- package/dist/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
- package/dist/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-bold.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-medium.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-regular.woff2 +0 -0
- package/dist/fonts/rubik/rubik-bold.woff2 +0 -0
- package/dist/fonts/rubik/rubik-medium.woff2 +0 -0
- package/dist/fonts/rubik/rubik-regular.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-bold.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-regular.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-semibold.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
- package/dist/icons/phosphor/LICENSE +21 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
- package/dist/icons/phosphor/bold/style.css +4627 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
- package/dist/icons/phosphor/duotone/style.css +12115 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
- package/dist/icons/phosphor/fill/style.css +4627 -0
- package/dist/icons/phosphor/light/Phosphor-Light.ttf +0 -0
- package/dist/icons/phosphor/light/Phosphor-Light.woff +0 -0
- package/dist/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
- package/dist/icons/phosphor/light/style.css +4627 -0
- package/dist/icons/phosphor/regular/Phosphor.ttf +0 -0
- package/dist/icons/phosphor/regular/Phosphor.woff +0 -0
- package/dist/icons/phosphor/regular/Phosphor.woff2 +0 -0
- package/dist/icons/phosphor/regular/style.css +4627 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
- package/dist/icons/phosphor/thin/style.css +4627 -0
- package/dist/vanduo.cjs.js +5569 -0
- package/dist/vanduo.cjs.js.map +7 -0
- package/dist/vanduo.cjs.min.js +48 -0
- package/dist/vanduo.cjs.min.js.map +7 -0
- package/dist/vanduo.css +60666 -0
- package/dist/vanduo.css.map +1 -0
- package/dist/vanduo.esm.js +5548 -0
- package/dist/vanduo.esm.js.map +7 -0
- package/dist/vanduo.esm.min.js +48 -0
- package/dist/vanduo.esm.min.js.map +7 -0
- package/dist/vanduo.js +5545 -0
- package/dist/vanduo.js.map +7 -0
- package/dist/vanduo.min.css +2 -0
- package/dist/vanduo.min.css.map +1 -0
- package/dist/vanduo.min.js +48 -0
- package/dist/vanduo.min.js.map +7 -0
- package/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
- package/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
- package/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
- package/fonts/inter/inter-bold.woff2 +0 -0
- package/fonts/inter/inter-medium.woff2 +0 -0
- package/fonts/inter/inter-regular.woff2 +0 -0
- package/fonts/inter/inter-semibold.woff2 +0 -0
- package/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
- package/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
- package/fonts/open-sans/open-sans-bold.woff2 +0 -0
- package/fonts/open-sans/open-sans-medium.woff2 +0 -0
- package/fonts/open-sans/open-sans-regular.woff2 +0 -0
- package/fonts/rubik/rubik-bold.woff2 +0 -0
- package/fonts/rubik/rubik-medium.woff2 +0 -0
- package/fonts/rubik/rubik-regular.woff2 +0 -0
- package/fonts/source-sans/source-sans-bold.woff2 +0 -0
- package/fonts/source-sans/source-sans-regular.woff2 +0 -0
- package/fonts/source-sans/source-sans-semibold.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
- package/icons/phosphor/LICENSE +21 -0
- package/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
- package/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
- package/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
- package/icons/phosphor/bold/style.css +4627 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
- package/icons/phosphor/duotone/style.css +12115 -0
- package/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
- package/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
- package/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
- package/icons/phosphor/fill/style.css +4627 -0
- package/icons/phosphor/light/Phosphor-Light.ttf +0 -0
- package/icons/phosphor/light/Phosphor-Light.woff +0 -0
- package/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
- package/icons/phosphor/light/style.css +4627 -0
- package/icons/phosphor/regular/Phosphor.ttf +0 -0
- package/icons/phosphor/regular/Phosphor.woff +0 -0
- package/icons/phosphor/regular/Phosphor.woff2 +0 -0
- package/icons/phosphor/regular/style.css +4627 -0
- package/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
- package/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
- package/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
- package/icons/phosphor/thin/style.css +4627 -0
- package/js/components/code-snippet.js +639 -0
- package/js/components/collapsible.js +226 -0
- package/js/components/doc-search.js +936 -0
- package/js/components/draggable.js +725 -0
- package/js/components/dropdown.js +362 -0
- package/js/components/font-switcher.js +253 -0
- package/js/components/grid.js +279 -0
- package/js/components/image-box.js +372 -0
- package/js/components/modals.js +367 -0
- package/js/components/navbar.js +264 -0
- package/js/components/pagination.js +286 -0
- package/js/components/parallax.js +216 -0
- package/js/components/preloader.js +183 -0
- package/js/components/select.js +444 -0
- package/js/components/sidenav.js +303 -0
- package/js/components/tabs.js +303 -0
- package/js/components/theme-customizer.js +784 -0
- package/js/components/theme-switcher.js +183 -0
- package/js/components/toast.js +343 -0
- package/js/components/tooltips.js +306 -0
- package/js/index.js +52 -0
- package/js/utils/helpers.js +306 -0
- package/js/utils/lifecycle.js +135 -0
- package/js/vanduo.js +120 -0
- 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
|
+
var 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,52 @@
|
|
|
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
|
+
|
|
49
|
+
// Re-export for ESM / CJS consumers
|
|
50
|
+
const Vanduo = window.Vanduo;
|
|
51
|
+
export { Vanduo };
|
|
52
|
+
export default Vanduo;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanduo Framework - Utility Helpers
|
|
3
|
+
* Common utility functions used across the framework
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if element exists
|
|
8
|
+
* @param {string|HTMLElement} selector - CSS selector or element
|
|
9
|
+
* @returns {HTMLElement|null}
|
|
10
|
+
*/
|
|
11
|
+
function $(selector) {
|
|
12
|
+
if (typeof selector === 'string') {
|
|
13
|
+
return document.querySelector(selector);
|
|
14
|
+
}
|
|
15
|
+
return selector;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get all elements matching selector
|
|
20
|
+
* @param {string} selector - CSS selector
|
|
21
|
+
* @returns {NodeList}
|
|
22
|
+
*/
|
|
23
|
+
function $$(selector) {
|
|
24
|
+
return document.querySelectorAll(selector);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wait for DOM to be ready
|
|
29
|
+
* @param {Function} callback - Function to execute when DOM is ready
|
|
30
|
+
*/
|
|
31
|
+
function ready(callback) {
|
|
32
|
+
if (document.readyState === 'loading') {
|
|
33
|
+
document.addEventListener('DOMContentLoaded', callback);
|
|
34
|
+
} else {
|
|
35
|
+
callback();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safely get a value from localStorage
|
|
41
|
+
* @param {string} key - Storage key
|
|
42
|
+
* @param {string|null} fallback - Fallback when storage is unavailable
|
|
43
|
+
* @returns {string|null}
|
|
44
|
+
*/
|
|
45
|
+
function safeStorageGet(key, fallback = null) {
|
|
46
|
+
try {
|
|
47
|
+
const value = localStorage.getItem(key);
|
|
48
|
+
return value !== null ? value : fallback;
|
|
49
|
+
} catch (_e) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Safely set a value in localStorage
|
|
56
|
+
* @param {string} key - Storage key
|
|
57
|
+
* @param {string} value - Value to store
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function safeStorageSet(key, value) {
|
|
61
|
+
try {
|
|
62
|
+
localStorage.setItem(key, value);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (_e) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add event listener with delegation support
|
|
71
|
+
* @param {string|HTMLElement} target - Target element or selector
|
|
72
|
+
* @param {string} event - Event type
|
|
73
|
+
* @param {string|Function} handlerOrSelector - Event handler or selector for delegation
|
|
74
|
+
* @param {Function} handler - Event handler (if using delegation)
|
|
75
|
+
*/
|
|
76
|
+
function on(target, event, handlerOrSelector, handler) {
|
|
77
|
+
const element = typeof target === 'string' ? $(target) : target;
|
|
78
|
+
|
|
79
|
+
if (!element) return;
|
|
80
|
+
|
|
81
|
+
if (typeof handlerOrSelector === 'function') {
|
|
82
|
+
// Direct event binding
|
|
83
|
+
element.addEventListener(event, handlerOrSelector);
|
|
84
|
+
} else {
|
|
85
|
+
// Event delegation
|
|
86
|
+
element.addEventListener(event, function (e) {
|
|
87
|
+
const delegateTarget = e.target.closest(handlerOrSelector);
|
|
88
|
+
if (delegateTarget && element.contains(delegateTarget)) {
|
|
89
|
+
handler.call(delegateTarget, e);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Remove event listener
|
|
97
|
+
* @param {string|HTMLElement} target - Target element or selector
|
|
98
|
+
* @param {string} event - Event type
|
|
99
|
+
* @param {Function} handler - Event handler
|
|
100
|
+
*/
|
|
101
|
+
function off(target, event, handler) {
|
|
102
|
+
const element = typeof target === 'string' ? $(target) : target;
|
|
103
|
+
if (element) {
|
|
104
|
+
element.removeEventListener(event, handler);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Toggle class on element
|
|
110
|
+
* @param {string|HTMLElement} selector - CSS selector or element
|
|
111
|
+
* @param {string} className - Class name to toggle
|
|
112
|
+
*/
|
|
113
|
+
function toggleClass(selector, className) {
|
|
114
|
+
const element = typeof selector === 'string' ? $(selector) : selector;
|
|
115
|
+
if (element) {
|
|
116
|
+
element.classList.toggle(className);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Add class to element
|
|
122
|
+
* @param {string|HTMLElement} selector - CSS selector or element
|
|
123
|
+
* @param {string} className - Class name to add
|
|
124
|
+
*/
|
|
125
|
+
function addClass(selector, className) {
|
|
126
|
+
const element = typeof selector === 'string' ? $(selector) : selector;
|
|
127
|
+
if (element) {
|
|
128
|
+
element.classList.add(className);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove class from element
|
|
134
|
+
* @param {string|HTMLElement} selector - CSS selector or element
|
|
135
|
+
* @param {string} className - Class name to remove
|
|
136
|
+
*/
|
|
137
|
+
function removeClass(selector, className) {
|
|
138
|
+
const element = typeof selector === 'string' ? $(selector) : selector;
|
|
139
|
+
if (element) {
|
|
140
|
+
element.classList.remove(className);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if element has class
|
|
146
|
+
* @param {string|HTMLElement} selector - CSS selector or element
|
|
147
|
+
* @param {string} className - Class name to check
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
function hasClass(selector, className) {
|
|
151
|
+
const element = typeof selector === 'string' ? $(selector) : selector;
|
|
152
|
+
return element ? element.classList.contains(className) : false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get or set data attribute
|
|
157
|
+
* @param {HTMLElement} element - Element
|
|
158
|
+
* @param {string} name - Data attribute name (without data- prefix)
|
|
159
|
+
* @param {string} value - Value to set (optional)
|
|
160
|
+
* @returns {string|undefined}
|
|
161
|
+
*/
|
|
162
|
+
function data(element, name, value) {
|
|
163
|
+
if (value !== undefined) {
|
|
164
|
+
element.setAttribute(`data-${name}`, value);
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
return element.getAttribute(`data-${name}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Debounce function
|
|
172
|
+
* @param {Function} func - Function to debounce
|
|
173
|
+
* @param {number} wait - Wait time in milliseconds
|
|
174
|
+
* @returns {Function}
|
|
175
|
+
*/
|
|
176
|
+
function debounce(func, wait) {
|
|
177
|
+
let timeout;
|
|
178
|
+
return function executedFunction(...args) {
|
|
179
|
+
const later = () => {
|
|
180
|
+
clearTimeout(timeout);
|
|
181
|
+
func(...args);
|
|
182
|
+
};
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
timeout = setTimeout(later, wait);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Throttle function
|
|
190
|
+
* @param {Function} func - Function to throttle
|
|
191
|
+
* @param {number} limit - Time limit in milliseconds
|
|
192
|
+
* @returns {Function}
|
|
193
|
+
*/
|
|
194
|
+
function throttle(func, limit) {
|
|
195
|
+
let inThrottle;
|
|
196
|
+
return function (...args) {
|
|
197
|
+
if (!inThrottle) {
|
|
198
|
+
func.apply(this, args);
|
|
199
|
+
inThrottle = true;
|
|
200
|
+
setTimeout(() => inThrottle = false, limit);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if element is visible
|
|
207
|
+
* @param {HTMLElement} element - Element to check
|
|
208
|
+
* @returns {boolean}
|
|
209
|
+
*/
|
|
210
|
+
function isVisible(element) {
|
|
211
|
+
if (!element) return false;
|
|
212
|
+
const style = window.getComputedStyle(element);
|
|
213
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get element position relative to viewport
|
|
218
|
+
* @param {HTMLElement} element - Element
|
|
219
|
+
* @returns {Object} - Object with top, left, right, bottom, width, height
|
|
220
|
+
*/
|
|
221
|
+
function getPosition(element) {
|
|
222
|
+
if (!element) return null;
|
|
223
|
+
const rect = element.getBoundingClientRect();
|
|
224
|
+
return {
|
|
225
|
+
top: rect.top,
|
|
226
|
+
left: rect.left,
|
|
227
|
+
right: rect.right,
|
|
228
|
+
bottom: rect.bottom,
|
|
229
|
+
width: rect.width,
|
|
230
|
+
height: rect.height
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Escape HTML special characters to prevent injection
|
|
236
|
+
* @param {string} str - String to escape
|
|
237
|
+
* @returns {string} Escaped string safe for insertion into HTML
|
|
238
|
+
*/
|
|
239
|
+
function escapeHtml(str) {
|
|
240
|
+
if (!str) return '';
|
|
241
|
+
var div = document.createElement('div');
|
|
242
|
+
div.appendChild(document.createTextNode(str));
|
|
243
|
+
return div.innerHTML;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Basic HTML sanitizer (whitelist-based) — runs in the browser without external libs.
|
|
248
|
+
* Keeps a small set of tags and strips disallowed tags and attributes. Safe for
|
|
249
|
+
* simple rich text (use server-side or DOMPurify for stronger guarantees).
|
|
250
|
+
* @param {string} input
|
|
251
|
+
* @returns {string} sanitized HTML
|
|
252
|
+
*/
|
|
253
|
+
function sanitizeHtml(input) {
|
|
254
|
+
if (!input) return '';
|
|
255
|
+
var doc = new DOMParser().parseFromString(input, 'text/html');
|
|
256
|
+
var allowed = ['B', 'STRONG', 'I', 'EM', 'BR', 'A', 'SPAN', 'U', 'SVG', 'PATH', 'LINE', 'CIRCLE', 'POLYLINE', 'RECT', 'G'];
|
|
257
|
+
|
|
258
|
+
var sanitizeNode = function (node) {
|
|
259
|
+
var children = Array.from(node.childNodes);
|
|
260
|
+
children.forEach(function (child) {
|
|
261
|
+
if (child.nodeType === Node.TEXT_NODE) return;
|
|
262
|
+
|
|
263
|
+
if (!allowed.includes(child.nodeName)) {
|
|
264
|
+
var text = document.createTextNode(child.textContent);
|
|
265
|
+
node.replaceChild(text, child);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (child.nodeName === 'A') {
|
|
270
|
+
var href = child.getAttribute('href') || '';
|
|
271
|
+
try {
|
|
272
|
+
var url = new URL(href, location.href);
|
|
273
|
+
if (!['http:', 'https:', 'mailto:'].includes(url.protocol)) {
|
|
274
|
+
child.removeAttribute('href');
|
|
275
|
+
}
|
|
276
|
+
} catch (_e) {
|
|
277
|
+
child.removeAttribute('href');
|
|
278
|
+
}
|
|
279
|
+
child.removeAttribute('target');
|
|
280
|
+
child.removeAttribute('rel');
|
|
281
|
+
} else if (child.nodeName === 'SVG' || child.closest && child.closest('svg')) {
|
|
282
|
+
// Allow safe SVG presentation attributes only
|
|
283
|
+
var safeSvgAttrs = ['xmlns', 'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width',
|
|
284
|
+
'stroke-linecap', 'stroke-linejoin', 'd', 'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2', 'points',
|
|
285
|
+
'transform', 'class'];
|
|
286
|
+
var attrs = Array.from(child.attributes || []);
|
|
287
|
+
attrs.forEach(function (a) {
|
|
288
|
+
if (!safeSvgAttrs.includes(a.name)) {
|
|
289
|
+
child.removeAttribute(a.name);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
} else {
|
|
293
|
+
var otherAttrs = Array.from(child.attributes || []);
|
|
294
|
+
otherAttrs.forEach(function (a) { child.removeAttribute(a.name); });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
sanitizeNode(child);
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
sanitizeNode(doc.body);
|
|
302
|
+
return doc.body.innerHTML;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|