@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.
- package/README.md +87 -41
- package/css/components/affix.css +1 -1
- package/css/components/alerts.css +40 -40
- package/css/components/avatar.css +33 -33
- package/css/components/badges.css +42 -42
- package/css/components/breadcrumbs.css +5 -5
- package/css/components/bubble.css +4 -4
- package/css/components/buttons.css +124 -124
- package/css/components/cards.css +10 -10
- package/css/components/chips.css +28 -28
- package/css/components/code-snippet.css +18 -18
- package/css/components/collapsible.css +28 -20
- package/css/components/collections.css +21 -21
- package/css/components/datepicker.css +13 -13
- package/css/components/doc-search.css +46 -53
- package/css/components/doc-tabs.css +10 -10
- package/css/components/draggable.css +34 -34
- package/css/components/dropdown.css +14 -14
- package/css/components/expanding-cards.css +1 -1
- package/css/components/fab.css +7 -7
- package/css/components/flow.css +3 -3
- package/css/components/footer.css +26 -26
- package/css/components/forms.css +95 -83
- package/css/components/image-box.css +13 -17
- package/css/components/modals.css +8 -8
- package/css/components/music-player.css +26 -26
- package/css/components/navbar.css +27 -27
- package/css/components/pagination.css +15 -15
- package/css/components/preloader.css +10 -10
- package/css/components/progress.css +8 -8
- package/css/components/rating.css +4 -4
- package/css/components/sidenav.css +14 -14
- package/css/components/skeleton.css +10 -9
- package/css/components/spinner.css +10 -10
- package/css/components/spotlight.css +7 -7
- package/css/components/stepper.css +13 -13
- package/css/components/suggest.css +10 -10
- package/css/components/tabs.css +22 -22
- package/css/components/theme-customizer.css +87 -87
- package/css/components/timeline.css +14 -14
- package/css/components/timepicker.css +7 -7
- package/css/components/toast.css +31 -31
- package/css/components/tooltips.css +11 -11
- package/css/components/transfer.css +12 -12
- package/css/components/tree.css +9 -9
- package/css/components/waypoint.css +3 -3
- package/css/core/colors.css +61 -35
- package/css/core/grid.css +1 -6
- package/css/core/helpers.css +11 -11
- package/css/core/tokens.css +114 -36
- package/css/core/typography.css +15 -13
- package/css/core/vd-aliases.css +100 -52
- package/css/effects/morph.css +5 -5
- package/css/utilities/media.css +2 -2
- package/css/utilities/table.css +34 -34
- package/css/utilities/transitions.css +22 -10
- package/css/vanduo.css +14 -34
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +935 -294
- package/dist/vanduo.cjs.js.map +3 -3
- package/dist/vanduo.cjs.min.js +7 -7
- package/dist/vanduo.cjs.min.js.map +3 -3
- package/dist/vanduo.css +7942 -7824
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +935 -294
- package/dist/vanduo.esm.js.map +3 -3
- package/dist/vanduo.esm.min.js +7 -7
- package/dist/vanduo.esm.min.js.map +3 -3
- package/dist/vanduo.js +935 -294
- package/dist/vanduo.js.map +3 -3
- package/dist/vanduo.min.css +2 -2
- package/dist/vanduo.min.css.map +1 -1
- package/dist/vanduo.min.js +7 -7
- package/dist/vanduo.min.js.map +3 -3
- package/js/components/affix.js +2 -2
- package/js/components/bubble.js +3 -3
- package/js/components/code-snippet.js +129 -5
- package/js/components/collapsible.js +2 -3
- package/js/components/datepicker.js +2 -2
- package/js/components/doc-search.js +69 -11
- package/js/components/draggable.js +4 -4
- package/js/components/dropdown.js +2 -3
- package/js/components/expanding-cards.js +2 -2
- package/js/components/flow.js +2 -2
- package/js/components/font-switcher.js +26 -16
- package/js/components/glass.js +2 -2
- package/js/components/grid.js +19 -8
- package/js/components/image-box.js +49 -10
- package/js/components/lazy-load.js +81 -9
- package/js/components/modals.js +28 -12
- package/js/components/morph.js +2 -2
- package/js/components/music-player.js +2 -2
- package/js/components/navbar.js +2 -2
- package/js/components/pagination.js +2 -3
- package/js/components/parallax.js +9 -10
- package/js/components/preloader.js +14 -5
- package/js/components/rating.js +2 -2
- package/js/components/ripple.js +2 -2
- package/js/components/select.js +2 -3
- package/js/components/sidenav.js +43 -14
- package/js/components/spotlight.js +2 -2
- package/js/components/stepper.js +2 -2
- package/js/components/suggest.js +9 -3
- package/js/components/tabs.js +2 -2
- package/js/components/theme-customizer.js +154 -23
- package/js/components/theme-switcher.js +27 -16
- package/js/components/timeline.js +41 -12
- package/js/components/timepicker.js +2 -2
- package/js/components/toast.js +1 -1
- package/js/components/tooltips.js +4 -4
- package/js/components/transfer.js +2 -2
- package/js/components/tree.js +2 -2
- package/js/components/validate.js +2 -2
- package/js/components/vd-hex.js +12 -6
- package/js/components/waypoint.js +2 -2
- package/js/utils/helpers.js +7 -4
- package/js/utils/lifecycle.js +158 -83
- package/js/vanduo.js +203 -34
- package/package.json +3 -4
package/js/utils/helpers.js
CHANGED
|
@@ -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
|
|
314
|
-
const safeAttrs = new Set(['class'
|
|
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
|
-
|
package/js/utils/lifecycle.js
CHANGED
|
@@ -1,135 +1,210 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vanduo Framework - Lifecycle Manager
|
|
3
|
-
* Central registry for
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
35
|
+
// Map<Element, Map<componentName, { cleanup, onDestroy, registeredAt }>>
|
|
16
36
|
instances: new Map(),
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
96
|
+
componentEntries.set(componentName, {
|
|
33
97
|
component: componentName,
|
|
34
|
-
cleanup:
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
toRemove.push(element);
|
|
148
|
+
if (componentEntries.has(componentName)) {
|
|
149
|
+
toRemove.push([element, componentName]);
|
|
69
150
|
}
|
|
70
151
|
});
|
|
71
152
|
|
|
72
|
-
toRemove.forEach(function(
|
|
73
|
-
Lifecycle.unregister(
|
|
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
|
-
|
|
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(
|
|
86
|
-
if (
|
|
87
|
-
|
|
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(
|
|
92
|
-
Lifecycle.unregister(
|
|
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(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
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.
|
|
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 "' +
|
|
245
|
+
console.warn('[Vanduo] Failed to reinitialize component "' + componentName + '":', e);
|
|
80
246
|
}
|
|
81
247
|
}
|
|
82
248
|
},
|
|
83
249
|
|
|
84
250
|
/**
|
|
85
|
-
* Destroy
|
|
86
|
-
* Uses lifecycle manager for memory leak prevention
|
|
251
|
+
* Destroy component instances within the provided root.
|
|
87
252
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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",
|