@vanduo-oss/framework 1.3.8 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +87 -41
  2. package/css/components/affix.css +1 -1
  3. package/css/components/alerts.css +40 -40
  4. package/css/components/avatar.css +33 -33
  5. package/css/components/badges.css +42 -42
  6. package/css/components/breadcrumbs.css +5 -5
  7. package/css/components/bubble.css +4 -4
  8. package/css/components/buttons.css +124 -124
  9. package/css/components/cards.css +10 -10
  10. package/css/components/chips.css +28 -28
  11. package/css/components/code-snippet.css +18 -18
  12. package/css/components/collapsible.css +28 -20
  13. package/css/components/collections.css +21 -21
  14. package/css/components/datepicker.css +13 -13
  15. package/css/components/doc-search.css +46 -53
  16. package/css/components/doc-tabs.css +10 -10
  17. package/css/components/draggable.css +34 -34
  18. package/css/components/dropdown.css +14 -14
  19. package/css/components/expanding-cards.css +1 -1
  20. package/css/components/fab.css +7 -7
  21. package/css/components/flow.css +3 -3
  22. package/css/components/footer.css +26 -26
  23. package/css/components/forms.css +95 -83
  24. package/css/components/image-box.css +13 -17
  25. package/css/components/modals.css +8 -8
  26. package/css/components/music-player.css +26 -26
  27. package/css/components/navbar.css +27 -27
  28. package/css/components/pagination.css +15 -15
  29. package/css/components/preloader.css +10 -10
  30. package/css/components/progress.css +8 -8
  31. package/css/components/rating.css +4 -4
  32. package/css/components/sidenav.css +14 -14
  33. package/css/components/skeleton.css +10 -9
  34. package/css/components/spinner.css +10 -10
  35. package/css/components/spotlight.css +7 -7
  36. package/css/components/stepper.css +13 -13
  37. package/css/components/suggest.css +10 -10
  38. package/css/components/tabs.css +22 -22
  39. package/css/components/theme-customizer.css +87 -87
  40. package/css/components/timeline.css +14 -14
  41. package/css/components/timepicker.css +7 -7
  42. package/css/components/toast.css +31 -31
  43. package/css/components/tooltips.css +11 -11
  44. package/css/components/transfer.css +12 -12
  45. package/css/components/tree.css +9 -9
  46. package/css/components/waypoint.css +3 -3
  47. package/css/core/colors.css +61 -35
  48. package/css/core/grid.css +1 -6
  49. package/css/core/helpers.css +11 -11
  50. package/css/core/tokens.css +114 -36
  51. package/css/core/typography.css +15 -13
  52. package/css/core/vd-aliases.css +100 -52
  53. package/css/effects/morph.css +5 -5
  54. package/css/utilities/media.css +2 -2
  55. package/css/utilities/table.css +34 -34
  56. package/css/utilities/transitions.css +22 -10
  57. package/css/vanduo.css +14 -34
  58. package/dist/build-info.json +3 -3
  59. package/dist/vanduo.cjs.js +935 -294
  60. package/dist/vanduo.cjs.js.map +3 -3
  61. package/dist/vanduo.cjs.min.js +7 -7
  62. package/dist/vanduo.cjs.min.js.map +3 -3
  63. package/dist/vanduo.css +7942 -7824
  64. package/dist/vanduo.css.map +1 -1
  65. package/dist/vanduo.esm.js +935 -294
  66. package/dist/vanduo.esm.js.map +3 -3
  67. package/dist/vanduo.esm.min.js +7 -7
  68. package/dist/vanduo.esm.min.js.map +3 -3
  69. package/dist/vanduo.js +935 -294
  70. package/dist/vanduo.js.map +3 -3
  71. package/dist/vanduo.min.css +2 -2
  72. package/dist/vanduo.min.css.map +1 -1
  73. package/dist/vanduo.min.js +7 -7
  74. package/dist/vanduo.min.js.map +3 -3
  75. package/js/components/affix.js +2 -2
  76. package/js/components/bubble.js +3 -3
  77. package/js/components/code-snippet.js +129 -5
  78. package/js/components/collapsible.js +2 -3
  79. package/js/components/datepicker.js +2 -2
  80. package/js/components/doc-search.js +69 -11
  81. package/js/components/draggable.js +4 -4
  82. package/js/components/dropdown.js +2 -3
  83. package/js/components/expanding-cards.js +2 -2
  84. package/js/components/flow.js +2 -2
  85. package/js/components/font-switcher.js +26 -16
  86. package/js/components/glass.js +2 -2
  87. package/js/components/grid.js +19 -8
  88. package/js/components/image-box.js +49 -10
  89. package/js/components/lazy-load.js +81 -9
  90. package/js/components/modals.js +28 -12
  91. package/js/components/morph.js +2 -2
  92. package/js/components/music-player.js +2 -2
  93. package/js/components/navbar.js +2 -2
  94. package/js/components/pagination.js +2 -3
  95. package/js/components/parallax.js +9 -10
  96. package/js/components/preloader.js +14 -5
  97. package/js/components/rating.js +2 -2
  98. package/js/components/ripple.js +2 -2
  99. package/js/components/select.js +2 -3
  100. package/js/components/sidenav.js +43 -14
  101. package/js/components/spotlight.js +2 -2
  102. package/js/components/stepper.js +2 -2
  103. package/js/components/suggest.js +9 -3
  104. package/js/components/tabs.js +2 -2
  105. package/js/components/theme-customizer.js +154 -23
  106. package/js/components/theme-switcher.js +27 -16
  107. package/js/components/timeline.js +41 -12
  108. package/js/components/timepicker.js +2 -2
  109. package/js/components/toast.js +1 -1
  110. package/js/components/tooltips.js +4 -4
  111. package/js/components/transfer.js +2 -2
  112. package/js/components/tree.js +2 -2
  113. package/js/components/validate.js +2 -2
  114. package/js/components/vd-hex.js +12 -6
  115. package/js/components/waypoint.js +2 -2
  116. package/js/utils/helpers.js +7 -4
  117. package/js/utils/lifecycle.js +158 -83
  118. package/js/vanduo.js +203 -34
  119. package/package.json +3 -4
@@ -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
  })();
package/js/vanduo.js CHANGED
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const VANDUO_VERSION = typeof __VANDUO_VERSION__ !== 'undefined' ? __VANDUO_VERSION__ : '0.0.0-dev';
9
+ const hasOwn = Object.prototype.hasOwnProperty;
9
10
 
10
11
  /**
11
12
  * Vanduo Framework Object
@@ -13,46 +14,193 @@
13
14
  const Vanduo = {
14
15
  version: VANDUO_VERSION,
15
16
  components: {},
17
+ aliases: {},
18
+ _decoratedComponents: new WeakSet(),
19
+
20
+ resolveComponentName: function (name) {
21
+ return this.aliases[name] || name;
22
+ },
23
+
24
+ _isRoot: function (root) {
25
+ if (typeof window.VanduoLifecycle !== 'undefined' && typeof window.VanduoLifecycle.isRoot === 'function') {
26
+ return window.VanduoLifecycle.isRoot(root);
27
+ }
28
+
29
+ return !!root && (root === document || root.nodeType === 1 || root.nodeType === 9 || root.nodeType === 11);
30
+ },
31
+
32
+ _normalizeRoot: function (root) {
33
+ return this._isRoot(root) ? root : document;
34
+ },
35
+
36
+ _queryAll: function (root, selector) {
37
+ const scope = this._normalizeRoot(root);
38
+ const matches = [];
39
+
40
+ if (scope instanceof Element && typeof scope.matches === 'function' && scope.matches(selector)) {
41
+ matches.push(scope);
42
+ }
43
+
44
+ if (typeof scope.querySelectorAll === 'function') {
45
+ const descendants = scope.querySelectorAll(selector);
46
+ for (let i = 0; i < descendants.length; i++) {
47
+ matches.push(descendants[i]);
48
+ }
49
+ }
50
+
51
+ return matches;
52
+ },
53
+
54
+ queryAll: function (root, selector) {
55
+ if (typeof selector === 'undefined') {
56
+ selector = root;
57
+ root = document;
58
+ }
59
+
60
+ return this._queryAll(root, selector);
61
+ },
62
+
63
+ queryOne: function (root, selector) {
64
+ const matches = this.queryAll(root, selector);
65
+ return matches.length ? matches[0] : null;
66
+ },
67
+
68
+ _isLifecycleManagedComponent: function (component) {
69
+ if (!component || typeof component !== 'object') return false;
70
+
71
+ for (const key in component) {
72
+ if (hasOwn.call(component, key) && component[key] instanceof Map) {
73
+ return true;
74
+ }
75
+ }
76
+
77
+ return false;
78
+ },
79
+
80
+ _syncComponentLifecycle: function (name, component, root) {
81
+ const lifecycle = window.VanduoLifecycle;
82
+ if (!lifecycle || !this._isLifecycleManagedComponent(component)) return;
83
+
84
+ const componentName = this.resolveComponentName(name);
85
+ const scope = this._normalizeRoot(root);
86
+
87
+ for (const key in component) {
88
+ if (!hasOwn.call(component, key) || !(component[key] instanceof Map)) {
89
+ continue;
90
+ }
91
+
92
+ component[key].forEach(function (instance, element) {
93
+ if (!(element instanceof Element) || !lifecycle.isInRoot(scope, element) || lifecycle.has(element, componentName)) {
94
+ return;
95
+ }
96
+
97
+ if (typeof component.destroy === 'function') {
98
+ lifecycle.register(element, componentName, [], function () {
99
+ component.destroy(element);
100
+ });
101
+ return;
102
+ }
103
+
104
+ const cleanup = instance && Array.isArray(instance.cleanup) ? instance.cleanup : [];
105
+ lifecycle.register(element, componentName, cleanup, function () {
106
+ component[key].delete(element);
107
+ });
108
+ });
109
+ }
110
+ },
111
+
112
+ _decorateComponent: function (name, component) {
113
+ const framework = this;
114
+ const lifecycle = window.VanduoLifecycle;
115
+ if (!component || typeof component !== 'object' || this._decoratedComponents.has(component)) {
116
+ return;
117
+ }
118
+
119
+ const originalInit = typeof component.init === 'function' ? component.init : null;
120
+ if (originalInit) {
121
+ component.init = function (...args) {
122
+ const scopedRoot = framework._isRoot(args[0]) ? args[0] : null;
123
+ const result = originalInit.apply(this, args);
124
+
125
+ if (window.Vanduo) {
126
+ const syncRoot = scopedRoot || document;
127
+ window.Vanduo._syncComponentLifecycle(name, this, syncRoot);
128
+ }
129
+
130
+ return result;
131
+ };
132
+ }
133
+
134
+ const originalDestroyAll = typeof component.destroyAll === 'function' ? component.destroyAll : null;
135
+ if (originalDestroyAll) {
136
+ component.destroyAll = function (...args) {
137
+ const scopedRoot = framework._isRoot(args[0]) ? args[0] : null;
138
+ const componentName = window.Vanduo ? window.Vanduo.resolveComponentName(name) : name;
139
+
140
+ if (lifecycle && window.Vanduo && window.Vanduo._isLifecycleManagedComponent(this)) {
141
+ if (scopedRoot && scopedRoot !== document) {
142
+ lifecycle.destroyAllInContainer(scopedRoot, componentName);
143
+ if (this.__vanduoScopedDestroyAll === true) {
144
+ return originalDestroyAll.apply(this, args);
145
+ }
146
+ return;
147
+ }
148
+
149
+ lifecycle.destroyAll(componentName);
150
+ }
151
+
152
+ return originalDestroyAll.apply(this, args);
153
+ };
154
+ }
155
+
156
+ this._decoratedComponents.add(component);
157
+ },
16
158
 
17
159
  /**
18
160
  * Initialize framework
19
161
  * Call this after DOM is ready and all components are loaded
20
162
  */
21
- init: function () {
22
- // Initialize components when DOM is ready
163
+ init: function (root) {
164
+ const scope = this._normalizeRoot(root);
165
+
166
+ if (scope !== document) {
167
+ this.initComponents(scope);
168
+ return;
169
+ }
170
+
23
171
  if (typeof ready !== 'undefined') {
24
172
  ready(() => {
25
- this.initComponents();
173
+ this.initComponents(document);
26
174
  });
27
- } else {
28
- // Fallback if helpers.js is not loaded
29
- if (document.readyState === 'loading') {
30
- document.addEventListener('DOMContentLoaded', () => {
31
- this.initComponents();
32
- });
33
- } else {
34
- this.initComponents();
35
- }
175
+ return;
36
176
  }
177
+
178
+ if (document.readyState === 'loading') {
179
+ document.addEventListener('DOMContentLoaded', () => {
180
+ this.initComponents(document);
181
+ });
182
+ return;
183
+ }
184
+
185
+ this.initComponents(document);
37
186
  },
38
187
 
39
188
  /**
40
189
  * Initialize all components
41
190
  */
42
- initComponents: function () {
43
- // Initialize all registered components
191
+ initComponents: function (root) {
192
+ const scope = this._normalizeRoot(root);
193
+
44
194
  Object.keys(this.components).forEach((name) => {
45
195
  const component = this.components[name];
46
196
  if (component.init && typeof component.init === 'function') {
47
197
  try {
48
- component.init();
198
+ component.init(scope);
49
199
  } catch (e) {
50
200
  console.warn('[Vanduo] Failed to initialize component "' + name + '":', e);
51
201
  }
52
202
  }
53
203
  });
54
-
55
- console.log('Vanduo Framework v' + this.version + ' initialized');
56
204
  },
57
205
 
58
206
  /**
@@ -60,49 +208,69 @@
60
208
  * @param {string} name - Component name
61
209
  * @param {Object} component - Component object with init method
62
210
  */
63
- register: function (name, component) {
211
+ register: function (name, component, options) {
212
+ const opts = options || {};
213
+ this._decorateComponent(name, component);
64
214
  this.components[name] = component;
65
- // Note: Components are NOT auto-initialized on registration
66
- // Call Vanduo.init() explicitly after all components are registered
215
+
216
+ if (Array.isArray(opts.aliases)) {
217
+ opts.aliases.forEach((alias) => {
218
+ this.aliases[alias] = name;
219
+ });
220
+ }
221
+ },
222
+
223
+ registerAlias: function (alias, name) {
224
+ const canonicalName = this.resolveComponentName(name);
225
+ if (this.components[canonicalName]) {
226
+ this.aliases[alias] = canonicalName;
227
+ }
67
228
  },
68
229
 
69
230
  /**
70
231
  * Re-initialize a component (useful after dynamic DOM changes)
71
232
  * @param {string} name - Component name
72
233
  */
73
- reinit: function (name) {
74
- const component = this.components[name];
234
+ reinit: function (name, root) {
235
+ const scope = this._normalizeRoot(root);
236
+ const componentName = this.resolveComponentName(name);
237
+ const component = this.components[componentName];
75
238
  if (component && component.init && typeof component.init === 'function') {
76
239
  try {
77
- component.init();
240
+ if (component.destroyAll && typeof component.destroyAll === 'function') {
241
+ component.destroyAll(scope);
242
+ }
243
+ component.init(scope);
78
244
  } catch (e) {
79
- console.warn('[Vanduo] Failed to reinitialize component "' + name + '":', e);
245
+ console.warn('[Vanduo] Failed to reinitialize component "' + componentName + '":', e);
80
246
  }
81
247
  }
82
248
  },
83
249
 
84
250
  /**
85
- * Destroy all component instances and clean up event listeners
86
- * Uses lifecycle manager for memory leak prevention
251
+ * Destroy component instances within the provided root.
87
252
  */
88
- destroyAll: function () {
89
- // First, destroy components that have their own destroyAll
253
+ destroy: function (root) {
254
+ const scope = this._normalizeRoot(root);
90
255
  const names = Object.keys(this.components);
256
+
91
257
  for (let i = 0; i < names.length; i++) {
92
258
  const component = this.components[names[i]];
93
259
  if (component && component.destroyAll && typeof component.destroyAll === 'function') {
94
260
  try {
95
- component.destroyAll();
261
+ component.destroyAll(scope);
96
262
  } catch (e) {
97
263
  console.warn('[Vanduo] Failed to destroy component "' + names[i] + '":', e);
98
264
  }
99
265
  }
100
266
  }
267
+ },
101
268
 
102
- // Then, cleanup any remaining registered elements via lifecycle manager
103
- if (typeof window.VanduoLifecycle !== 'undefined') {
104
- window.VanduoLifecycle.destroyAll();
105
- }
269
+ /**
270
+ * Destroy all component instances and clean up event listeners.
271
+ */
272
+ destroyAll: function () {
273
+ this.destroy(document);
106
274
  },
107
275
 
108
276
  /**
@@ -111,7 +279,8 @@
111
279
  * @returns {Object|null}
112
280
  */
113
281
  getComponent: function (name) {
114
- return this.components[name] || null;
282
+ const componentName = this.resolveComponentName(name);
283
+ return this.components[componentName] || null;
115
284
  }
116
285
  };
117
286
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanduo-oss/framework",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
4
4
  "description": "Zero-dependency CSS/JS framework built on Fibonacci/Golden Ratio design system with Open Color integration",
5
5
  "keywords": [
6
6
  "css",
@@ -45,7 +45,7 @@
45
45
  "eslint": "^10.3.0",
46
46
  "husky": "^9.1.7",
47
47
  "lightningcss": "^1.32.0",
48
- "stylelint": "^17.10.0",
48
+ "stylelint": "^17.11.0",
49
49
  "stylelint-config-standard": "^40.0.0"
50
50
  },
51
51
  "engines": {
@@ -54,10 +54,9 @@
54
54
  },
55
55
  "scripts": {
56
56
  "build": "node scripts/build.js",
57
- "build:hex-grid": "pnpm --filter @vanduo-oss/hex-grid build",
58
- "build:packages": "pnpm -r --filter ./packages/* build",
59
57
  "build:min": "node scripts/build.js --minify",
60
58
  "dev": "node scripts/build.js --development",
59
+ "stats:css": "node scripts/stats-css.js",
61
60
  "check:versions": "node scripts/check-version-consistency.js",
62
61
  "release:assets": "node scripts/upload-release-assets.js",
63
62
  "lint": "pnpm run lint:css && pnpm run lint:js",