@vanduo-oss/framework 1.3.9 → 1.4.1

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 (124) hide show
  1. package/README.md +89 -42
  2. package/css/components/affix.css +12 -12
  3. package/css/components/alerts.css +70 -70
  4. package/css/components/avatar.css +78 -78
  5. package/css/components/badges.css +67 -67
  6. package/css/components/breadcrumbs.css +32 -32
  7. package/css/components/bubble.css +40 -40
  8. package/css/components/button-group.css +2 -2
  9. package/css/components/buttons.css +157 -157
  10. package/css/components/cards.css +79 -79
  11. package/css/components/chips.css +50 -50
  12. package/css/components/code-snippet.css +132 -132
  13. package/css/components/collapsible.css +67 -67
  14. package/css/components/collections.css +68 -68
  15. package/css/components/datepicker.css +54 -54
  16. package/css/components/doc-search.css +96 -103
  17. package/css/components/doc-tabs.css +11 -11
  18. package/css/components/draggable.css +77 -77
  19. package/css/components/dropdown.css +50 -50
  20. package/css/components/expanding-cards.css +1 -1
  21. package/css/components/fab.css +61 -61
  22. package/css/components/flow.css +55 -55
  23. package/css/components/footer.css +62 -62
  24. package/css/components/forms.css +437 -425
  25. package/css/components/image-box.css +50 -54
  26. package/css/components/modals.css +51 -51
  27. package/css/components/music-player.css +150 -150
  28. package/css/components/navbar.css +80 -80
  29. package/css/components/pagination.css +51 -51
  30. package/css/components/preloader.css +19 -19
  31. package/css/components/progress.css +20 -20
  32. package/css/components/rating.css +19 -19
  33. package/css/components/ripple.css +10 -10
  34. package/css/components/sidenav.css +72 -72
  35. package/css/components/skeleton.css +17 -16
  36. package/css/components/spinner.css +33 -33
  37. package/css/components/spotlight.css +33 -33
  38. package/css/components/stepper.css +39 -39
  39. package/css/components/suggest.css +37 -37
  40. package/css/components/tabs.css +60 -60
  41. package/css/components/theme-customizer.css +154 -154
  42. package/css/components/timeline.css +50 -50
  43. package/css/components/timepicker.css +29 -29
  44. package/css/components/toast.css +53 -53
  45. package/css/components/tooltips.css +78 -78
  46. package/css/components/transfer.css +37 -37
  47. package/css/components/tree.css +28 -28
  48. package/css/components/waypoint.css +12 -12
  49. package/css/core/colors.css +640 -640
  50. package/css/core/grid.css +127 -132
  51. package/css/core/helpers.css +349 -349
  52. package/css/core/tokens.css +133 -67
  53. package/css/core/typography.css +105 -103
  54. package/css/effects/morph.css +21 -21
  55. package/css/effects/parallax.css +6 -6
  56. package/css/utilities/color-utilities.css +273 -273
  57. package/css/utilities/media.css +4 -4
  58. package/css/utilities/shadow.css +75 -75
  59. package/css/utilities/table.css +64 -64
  60. package/css/utilities/transitions.css +53 -41
  61. package/css/vanduo.css +14 -35
  62. package/dist/build-info.json +3 -3
  63. package/dist/vanduo.cjs.js +947 -307
  64. package/dist/vanduo.cjs.js.map +3 -3
  65. package/dist/vanduo.cjs.min.js +7 -7
  66. package/dist/vanduo.cjs.min.js.map +3 -3
  67. package/dist/vanduo.css +9650 -9656
  68. package/dist/vanduo.css.map +1 -1
  69. package/dist/vanduo.esm.js +947 -307
  70. package/dist/vanduo.esm.js.map +3 -3
  71. package/dist/vanduo.esm.min.js +7 -7
  72. package/dist/vanduo.esm.min.js.map +3 -3
  73. package/dist/vanduo.js +947 -307
  74. package/dist/vanduo.js.map +3 -3
  75. package/dist/vanduo.min.css +2 -2
  76. package/dist/vanduo.min.css.map +1 -1
  77. package/dist/vanduo.min.js +7 -7
  78. package/dist/vanduo.min.js.map +3 -3
  79. package/js/components/affix.js +4 -4
  80. package/js/components/bubble.js +3 -3
  81. package/js/components/code-snippet.js +129 -5
  82. package/js/components/collapsible.js +2 -3
  83. package/js/components/datepicker.js +2 -2
  84. package/js/components/doc-search.js +69 -11
  85. package/js/components/draggable.js +4 -4
  86. package/js/components/dropdown.js +2 -3
  87. package/js/components/expanding-cards.js +2 -2
  88. package/js/components/flow.js +2 -2
  89. package/js/components/font-switcher.js +23 -13
  90. package/js/components/glass.js +2 -2
  91. package/js/components/grid.js +19 -8
  92. package/js/components/image-box.js +51 -12
  93. package/js/components/lazy-load.js +81 -9
  94. package/js/components/modals.js +28 -12
  95. package/js/components/morph.js +3 -3
  96. package/js/components/music-player.js +13 -13
  97. package/js/components/navbar.js +3 -3
  98. package/js/components/pagination.js +2 -3
  99. package/js/components/parallax.js +9 -10
  100. package/js/components/preloader.js +15 -6
  101. package/js/components/rating.js +2 -2
  102. package/js/components/ripple.js +2 -2
  103. package/js/components/select.js +2 -3
  104. package/js/components/sidenav.js +43 -14
  105. package/js/components/spotlight.js +2 -2
  106. package/js/components/stepper.js +2 -2
  107. package/js/components/suggest.js +9 -3
  108. package/js/components/tabs.js +2 -2
  109. package/js/components/theme-customizer.js +155 -25
  110. package/js/components/theme-switcher.js +27 -16
  111. package/js/components/timeline.js +41 -12
  112. package/js/components/timepicker.js +2 -2
  113. package/js/components/toast.js +1 -1
  114. package/js/components/tooltips.js +4 -4
  115. package/js/components/transfer.js +2 -2
  116. package/js/components/tree.js +2 -2
  117. package/js/components/validate.js +2 -2
  118. package/js/components/vd-hex.js +10 -6
  119. package/js/components/waypoint.js +2 -2
  120. package/js/utils/helpers.js +7 -4
  121. package/js/utils/lifecycle.js +158 -83
  122. package/js/vanduo.js +203 -34
  123. package/package.json +2 -1
  124. package/css/core/vd-aliases.css +0 -60
@@ -94,7 +94,7 @@
94
94
  if (config.icon) {
95
95
  const allowSvg = config.iconAllowSvg === true;
96
96
  const safeIcon = typeof sanitizeHtml === 'function'
97
- ? sanitizeHtml(config.icon, { allowSvg })
97
+ ? sanitizeHtml(config.icon, { allowSvg, allowStyle: false })
98
98
  : escapeHtml(config.icon);
99
99
  html += `<span class="vd-toast-icon">${safeIcon}</span>`;
100
100
  } else if (config.type) {
@@ -31,8 +31,8 @@
31
31
  /**
32
32
  * Initialize tooltips
33
33
  */
34
- init: function () {
35
- const elements = document.querySelectorAll('[data-tooltip], [data-tooltip-html]');
34
+ init: function (root) {
35
+ const elements = window.Vanduo.queryAll(root, '[data-tooltip], [data-tooltip-html]');
36
36
 
37
37
  elements.forEach(element => {
38
38
  if (this.tooltips.has(element)) {
@@ -93,7 +93,7 @@
93
93
 
94
94
  if (htmlContent) {
95
95
  const allowSvg = element.hasAttribute('data-tooltip-allow-svg');
96
- tooltip.innerHTML = this.sanitizeHtml(htmlContent, { allowSvg });
96
+ tooltip.innerHTML = this.sanitizeHtml(htmlContent, { allowSvg, allowStyle: false });
97
97
  tooltip.classList.add('vd-tooltip-html');
98
98
  } else if (textContent) {
99
99
  tooltip.textContent = textContent;
@@ -253,7 +253,7 @@
253
253
  const { tooltip } = this.tooltips.get(el);
254
254
  if (isHtml) {
255
255
  const allowSvg = el.hasAttribute('data-tooltip-allow-svg');
256
- tooltip.innerHTML = this.sanitizeHtml(content, { allowSvg });
256
+ tooltip.innerHTML = this.sanitizeHtml(content, { allowSvg, allowStyle: false });
257
257
  tooltip.classList.add('vd-tooltip-html');
258
258
  } else {
259
259
  tooltip.textContent = content;
@@ -9,8 +9,8 @@
9
9
  const Transfer = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const transfers = document.querySelectorAll('[data-vd-transfer]');
12
+ init: function (root) {
13
+ const transfers = window.Vanduo.queryAll(root, '[data-vd-transfer]');
14
14
  transfers.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -9,8 +9,8 @@
9
9
  const Tree = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const trees = document.querySelectorAll('[data-vd-tree]');
12
+ init: function (root) {
13
+ const trees = window.Vanduo.queryAll(root, '[data-vd-tree]');
14
14
  trees.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -49,8 +49,8 @@
49
49
  match: 'Fields do not match'
50
50
  },
51
51
 
52
- init: function () {
53
- const forms = document.querySelectorAll('[data-vd-validate], .vd-validate');
52
+ init: function (root) {
53
+ const forms = window.Vanduo.queryAll(root, '[data-vd-validate], .vd-validate');
54
54
  forms.forEach(form => {
55
55
  if (this.instances.has(form)) return;
56
56
  this.initInstance(form);
@@ -90,13 +90,17 @@ export class VdHexGrid {
90
90
  const root = document.documentElement;
91
91
  const style = getComputedStyle(root);
92
92
 
93
+ const readToken = (token, defaultValue) => {
94
+ return style.getPropertyValue(token).trim() || defaultValue;
95
+ };
96
+
93
97
  return {
94
- bgPrimary: style.getPropertyValue('--bg-primary').trim() || '#ffffff',
95
- bgSecondary: style.getPropertyValue('--bg-secondary').trim() || '#f5f5f5',
96
- borderColor: style.getPropertyValue('--border-color').trim() || '#e0e0e0',
97
- colorPrimary: style.getPropertyValue('--color-primary').trim() || '#3b82f6',
98
- textColor: style.getPropertyValue('--text-primary').trim() || '#1f2937',
99
- textMuted: style.getPropertyValue('--text-muted').trim() || '#6b7280'
98
+ bgPrimary: readToken('--vd-bg-primary', '#ffffff'),
99
+ bgSecondary: readToken('--vd-bg-secondary', '#f5f5f5'),
100
+ borderColor: readToken('--vd-border-color', '#e0e0e0'),
101
+ colorPrimary: readToken('--vd-color-primary', '#3b82f6'),
102
+ textColor: readToken('--vd-text-primary', '#1f2937'),
103
+ textMuted: readToken('--vd-text-muted', '#6b7280')
100
104
  };
101
105
  }
102
106
 
@@ -9,8 +9,8 @@
9
9
  const Waypoint = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const navs = document.querySelectorAll('[data-vd-waypoint-nav], [data-vd-scrollspy-nav]');
12
+ init: function (root) {
13
+ const navs = window.Vanduo.queryAll(root, '[data-vd-waypoint-nav], [data-vd-scrollspy-nav]');
14
14
  navs.forEach(nav => {
15
15
  if (this.instances.has(nav)) return;
16
16
  this.initInstance(nav);
@@ -258,7 +258,7 @@ function escapeHtml(str) {
258
258
  * Keeps a small set of tags and strips disallowed tags and attributes. Safe for
259
259
  * simple rich text (use server-side or DOMPurify for stronger guarantees).
260
260
  * @param {string} input
261
- * @param {{ allowSvg?: boolean }} [options]
261
+ * @param {{ allowSvg?: boolean, allowStyle?: boolean }} [options]
262
262
  * @returns {string} sanitized HTML
263
263
  */
264
264
  function sanitizeHtml(input, options = {}) {
@@ -271,6 +271,7 @@ function sanitizeHtml(input, options = {}) {
271
271
  return escapeHtml(input);
272
272
  }
273
273
  const allowSvg = options && options.allowSvg === true;
274
+ const allowStyle = !options || options.allowStyle !== false;
274
275
  const baseAllowed = ['B', 'STRONG', 'I', 'EM', 'BR', 'A', 'SPAN', 'U', 'DIV', 'P', 'KBD', 'CODE', 'SMALL', 'MARK'];
275
276
  const svgAllowed = ['SVG', 'PATH', 'LINE', 'CIRCLE', 'POLYLINE', 'RECT', 'G'];
276
277
  const allowed = allowSvg ? baseAllowed.concat(svgAllowed) : baseAllowed;
@@ -310,8 +311,11 @@ function sanitizeHtml(input, options = {}) {
310
311
  }
311
312
  });
312
313
  } else {
313
- // Keep class and style; strip everything else (no event handlers, no src, etc.)
314
- const safeAttrs = new Set(['class', 'style']);
314
+ // Keep class and optionally inline style; strip everything else.
315
+ const safeAttrs = new Set(['class']);
316
+ if (allowStyle) {
317
+ safeAttrs.add('style');
318
+ }
315
319
  const otherAttrs = Array.from(child.attributes || []);
316
320
  otherAttrs.forEach(function (a) {
317
321
  if (!safeAttrs.has(a.name)) { child.removeAttribute(a.name); }
@@ -327,4 +331,3 @@ function sanitizeHtml(input, options = {}) {
327
331
  }
328
332
 
329
333
 
330
-
@@ -1,135 +1,210 @@
1
1
  /**
2
2
  * Vanduo Framework - Lifecycle Manager
3
- * Central registry for component instances and cleanup
4
- * Prevents memory leaks in SPAs by tracking event listeners
3
+ * Central registry for scoped init/destroy, instance cleanup, and DOM queries.
5
4
  */
6
5
 
7
- (function() {
6
+ (function () {
8
7
  'use strict';
9
8
 
10
- /**
11
- * Lifecycle Manager
12
- * Simple registry that tracks component instances and their cleanup functions
13
- */
9
+ function normalizeCallbacks(value) {
10
+ if (!value) return [];
11
+ if (Array.isArray(value)) {
12
+ return value.filter(function (fn) {
13
+ return typeof fn === 'function';
14
+ });
15
+ }
16
+ return typeof value === 'function' ? [value] : [];
17
+ }
18
+
19
+ function normalizeOptions(options) {
20
+ if (typeof options === 'function') {
21
+ return { onDestroy: [options] };
22
+ }
23
+ return options || {};
24
+ }
25
+
26
+ function callSafely(label, fn) {
27
+ try {
28
+ fn();
29
+ } catch (error) {
30
+ console.warn('[Vanduo Lifecycle] ' + label + ' error:', error);
31
+ }
32
+ }
33
+
14
34
  const Lifecycle = {
15
- // Map of element -> { componentName, cleanupFunctions }
35
+ // Map<Element, Map<componentName, { cleanup, onDestroy, registeredAt }>>
16
36
  instances: new Map(),
17
37
 
18
- /**
19
- * Register a component instance
20
- * @param {HTMLElement} element - The DOM element
21
- * @param {string} componentName - Name of the component
22
- * @param {Array<Function>} cleanupFns - Functions to call on destroy
23
- */
24
- register: function(element, componentName, cleanupFns = []) {
25
- if (this.instances.has(element)) {
26
- // Already registered, merge cleanup functions
27
- const existing = this.instances.get(element);
28
- existing.cleanup = existing.cleanup.concat(cleanupFns);
38
+ isRoot: function (root) {
39
+ return !!root && (root === document || root.nodeType === 1 || root.nodeType === 9 || root.nodeType === 11);
40
+ },
41
+
42
+ normalizeRoot: function (root) {
43
+ return this.isRoot(root) ? root : document;
44
+ },
45
+
46
+ isInRoot: function (root, element) {
47
+ const scope = this.normalizeRoot(root);
48
+ if (!(element instanceof Element)) return false;
49
+ if (scope === document) {
50
+ return document.documentElement ? document.documentElement.contains(element) : document.contains(element);
51
+ }
52
+ if (scope === element) return true;
53
+ return typeof scope.contains === 'function' && scope.contains(element);
54
+ },
55
+
56
+ queryAll: function (root, selector) {
57
+ const scope = this.normalizeRoot(root);
58
+ const matches = [];
59
+ if (scope instanceof Element && typeof scope.matches === 'function' && scope.matches(selector)) {
60
+ matches.push(scope);
61
+ }
62
+ if (typeof scope.querySelectorAll === 'function') {
63
+ const descendants = scope.querySelectorAll(selector);
64
+ for (let i = 0; i < descendants.length; i++) {
65
+ matches.push(descendants[i]);
66
+ }
67
+ }
68
+ return matches;
69
+ },
70
+
71
+ queryOne: function (root, selector) {
72
+ const matches = this.queryAll(root, selector);
73
+ return matches.length ? matches[0] : null;
74
+ },
75
+
76
+ runInRoot: function (root, fn) {
77
+ const scope = this.normalizeRoot(root);
78
+ return fn(scope);
79
+ },
80
+
81
+ register: function (element, componentName, cleanupFns, options) {
82
+ if (!(element instanceof Element) || !componentName) return;
83
+
84
+ const optionBag = normalizeOptions(options);
85
+ const cleanup = normalizeCallbacks(cleanupFns);
86
+ const onDestroy = normalizeCallbacks(optionBag.onDestroy);
87
+ const componentEntries = this.instances.get(element) || new Map();
88
+ const existing = componentEntries.get(componentName);
89
+
90
+ if (existing) {
91
+ existing.cleanup = existing.cleanup.concat(cleanup);
92
+ existing.onDestroy = existing.onDestroy.concat(onDestroy);
29
93
  return;
30
94
  }
31
95
 
32
- this.instances.set(element, {
96
+ componentEntries.set(componentName, {
33
97
  component: componentName,
34
- cleanup: cleanupFns,
98
+ cleanup: cleanup,
99
+ onDestroy: onDestroy,
35
100
  registeredAt: Date.now()
36
101
  });
102
+
103
+ this.instances.set(element, componentEntries);
37
104
  },
38
105
 
39
- /**
40
- * Unregister a single element and run its cleanup
41
- * @param {HTMLElement} element - The element to unregister
42
- */
43
- unregister: function(element) {
44
- const instance = this.instances.get(element);
45
- if (!instance) return;
46
-
47
- // Run all cleanup functions
48
- instance.cleanup.forEach(function(fn) {
49
- try {
50
- fn();
51
- } catch (e) {
52
- console.warn('[Vanduo Lifecycle] Cleanup error:', e);
106
+ unregister: function (element, componentName) {
107
+ const componentEntries = this.instances.get(element);
108
+ if (!componentEntries) return;
109
+
110
+ if (componentName) {
111
+ const entry = componentEntries.get(componentName);
112
+ if (!entry) return;
113
+
114
+ componentEntries.delete(componentName);
115
+ if (!componentEntries.size) {
116
+ this.instances.delete(element);
53
117
  }
54
- });
55
118
 
119
+ entry.cleanup.forEach(function (fn) {
120
+ callSafely('Cleanup', fn);
121
+ });
122
+ entry.onDestroy.forEach(function (fn) {
123
+ callSafely('Destroy', fn);
124
+ });
125
+ return;
126
+ }
127
+
128
+ const entries = Array.from(componentEntries.values());
56
129
  this.instances.delete(element);
130
+ entries.forEach(function (entry) {
131
+ entry.cleanup.forEach(function (fn) {
132
+ callSafely('Cleanup', fn);
133
+ });
134
+ entry.onDestroy.forEach(function (fn) {
135
+ callSafely('Destroy', fn);
136
+ });
137
+ });
57
138
  },
58
139
 
59
- /**
60
- * Destroy all instances of a specific component
61
- * @param {string} componentName - Optional component name filter
62
- */
63
- destroyAll: function(componentName) {
140
+ destroyAll: function (componentName) {
64
141
  const toRemove = [];
142
+ this.instances.forEach(function (componentEntries, element) {
143
+ if (!componentName) {
144
+ toRemove.push([element, null]);
145
+ return;
146
+ }
65
147
 
66
- this.instances.forEach(function(instance, element) {
67
- if (!componentName || instance.component === componentName) {
68
- toRemove.push(element);
148
+ if (componentEntries.has(componentName)) {
149
+ toRemove.push([element, componentName]);
69
150
  }
70
151
  });
71
152
 
72
- toRemove.forEach(function(element) {
73
- Lifecycle.unregister(element);
153
+ toRemove.forEach(function (entry) {
154
+ Lifecycle.unregister(entry[0], entry[1] || undefined);
74
155
  });
156
+
157
+ return toRemove.length;
75
158
  },
76
159
 
77
- /**
78
- * Destroy all instances within a specific container
79
- * Useful for SPAs when navigating between pages
80
- * @param {HTMLElement} container - Container element
81
- */
82
- destroyAllInContainer: function(container) {
160
+ destroyAllInContainer: function (container, componentName) {
161
+ const scope = this.normalizeRoot(container);
83
162
  const toRemove = [];
84
163
 
85
- this.instances.forEach(function(instance, element) {
86
- if (container.contains(element)) {
87
- toRemove.push(element);
164
+ this.instances.forEach(function (componentEntries, element) {
165
+ if (!Lifecycle.isInRoot(scope, element)) return;
166
+
167
+ if (!componentName) {
168
+ toRemove.push([element, null]);
169
+ return;
170
+ }
171
+
172
+ if (componentEntries.has(componentName)) {
173
+ toRemove.push([element, componentName]);
88
174
  }
89
175
  });
90
176
 
91
- toRemove.forEach(function(element) {
92
- Lifecycle.unregister(element);
177
+ toRemove.forEach(function (entry) {
178
+ Lifecycle.unregister(entry[0], entry[1] || undefined);
93
179
  });
180
+
181
+ return toRemove.length;
94
182
  },
95
183
 
96
- /**
97
- * Get all registered instances (for debugging)
98
- * @returns {Array} Array of instance info objects
99
- */
100
- getAll: function() {
184
+ getAll: function () {
101
185
  const result = [];
102
- this.instances.forEach(function(instance, element) {
103
- result.push({
104
- element: element,
105
- component: instance.component,
106
- registeredAt: instance.registeredAt
186
+ this.instances.forEach(function (componentEntries, element) {
187
+ componentEntries.forEach(function (entry) {
188
+ result.push({
189
+ element: element,
190
+ component: entry.component,
191
+ registeredAt: entry.registeredAt
192
+ });
107
193
  });
108
194
  });
109
195
  return result;
110
196
  },
111
197
 
112
- /**
113
- * Check if an element is registered
114
- * @param {HTMLElement} element - The element to check
115
- * @returns {boolean}
116
- */
117
- has: function(element) {
118
- return this.instances.has(element);
198
+ has: function (element, componentName) {
199
+ const componentEntries = this.instances.get(element);
200
+ if (!componentEntries) return false;
201
+ return componentName ? componentEntries.has(componentName) : componentEntries.size > 0;
119
202
  }
120
203
  };
121
204
 
122
- // Auto-cleanup on page unload
123
- window.addEventListener('beforeunload', function() {
205
+ window.addEventListener('beforeunload', function () {
124
206
  Lifecycle.destroyAll();
125
207
  });
126
208
 
127
- // Expose globally
128
209
  window.VanduoLifecycle = Lifecycle;
129
-
130
- // Register with Vanduo framework if available
131
- if (typeof window.Vanduo !== 'undefined') {
132
- window.Vanduo.register('lifecycle', Lifecycle);
133
- }
134
-
135
210
  })();