vanduo-framework 1.1.8 → 1.2.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.
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Vanduo Framework – LazyLoad Component
3
+ * v1.2.1
4
+ *
5
+ * Provides two levels of API:
6
+ *
7
+ * LOW-LEVEL (generic IntersectionObserver wrapper)
8
+ * VanduoLazyLoad.observe(element, callback, options?)
9
+ * VanduoLazyLoad.unobserve(element)
10
+ * VanduoLazyLoad.unobserveAll()
11
+ *
12
+ * HIGH-LEVEL (HTML-section fetcher, mirrors vanduo-docs SPA behaviour)
13
+ * VanduoLazyLoad.loadSection(url, containerEl, options?)
14
+ * options: { placeholder, threshold, rootMargin, onLoaded, onError }
15
+ * placeholder: 'skeleton' | 'spinner' | HTMLString (default: 'skeleton')
16
+ *
17
+ * ATTRIBUTE-DRIVEN (zero-JS usage, wired by init())
18
+ * <div data-vd-lazy="./path/to/section.html"
19
+ * data-vd-lazy-placeholder="skeleton|spinner">…</div>
20
+ *
21
+ * EVENTS dispatched on the host element:
22
+ * lazysection:loading — fetch started
23
+ * lazysection:loaded — content injected
24
+ * lazysection:error — fetch failed
25
+ */
26
+
27
+ (function () {
28
+ 'use strict';
29
+
30
+ /* ── Private state ────────────────────────────────── */
31
+
32
+ /** @type {Map<Element, IntersectionObserver>} */
33
+ const _observerMap = new Map();
34
+
35
+ /* ── Security helpers ─────────────────────────────── */
36
+
37
+ /**
38
+ * Returns true only if `url` resolves to the same origin as the page
39
+ * (relative paths and same-origin absolute URLs are allowed).
40
+ * Cross-origin URLs are rejected to prevent SSRF-style fetch abuse.
41
+ * @param {string} url
42
+ * @returns {boolean}
43
+ */
44
+ function _isSafeUrl(url) {
45
+ try {
46
+ // Relative URLs (no origin) are always safe
47
+ const resolved = new URL(url, window.location.href);
48
+ return resolved.origin === window.location.origin;
49
+ } catch (_) {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Safely inject fetched HTML into a container by parsing it with
56
+ * DOMParser (avoids script execution) and replacing children via
57
+ * standard DOM APIs instead of raw innerHTML assignment.
58
+ * @param {Element} containerEl
59
+ * @param {string} html
60
+ */
61
+ function _safeInjectHtml(containerEl, html) {
62
+ const parser = new DOMParser();
63
+ const doc = parser.parseFromString(html.trim(), 'text/html'); // CodeQL [js/xss-through-dom] Intentional: parsed nodes are sanitized below before adoption into live DOM
64
+
65
+ const DANGEROUS_TAGS = ['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'FORM', 'BASE', 'LINK', 'META', 'STYLE'];
66
+ for (const tag of DANGEROUS_TAGS) {
67
+ const els = doc.querySelectorAll(tag);
68
+ for (let i = els.length - 1; i >= 0; i--) {
69
+ els[i].parentNode.removeChild(els[i]);
70
+ }
71
+ }
72
+
73
+ // Strip dangerous attributes (e.g. onerror, javascript: urls) from all elements
74
+ function _sanitizeNode(node) {
75
+ if (node.nodeType === Node.ELEMENT_NODE) {
76
+ const attrs = node.attributes;
77
+ for (let i = attrs.length - 1; i >= 0; i--) {
78
+ const attrName = attrs[i].name.toLowerCase();
79
+ const attrValue = attrs[i].value.toLowerCase();
80
+ const trimmedValue = attrValue.trim();
81
+ if (
82
+ attrName.startsWith('on') ||
83
+ trimmedValue.startsWith('javascript:') ||
84
+ trimmedValue.startsWith('data:') ||
85
+ trimmedValue.startsWith('vbscript:')
86
+ ) {
87
+ node.removeAttribute(attrs[i].name);
88
+ }
89
+ }
90
+ const children = node.childNodes;
91
+ for (let i = 0; i < children.length; i++) {
92
+ _sanitizeNode(children[i]);
93
+ }
94
+ }
95
+ }
96
+ _sanitizeNode(doc.body);
97
+
98
+ // Collect all top-level body children
99
+ const nodes = Array.from(doc.body.childNodes);
100
+ // Clear container and append parsed nodes
101
+ while (containerEl.firstChild) {
102
+ containerEl.removeChild(containerEl.firstChild);
103
+ }
104
+ nodes.forEach(function (node) {
105
+ containerEl.appendChild(document.adoptNode(node));
106
+ });
107
+ }
108
+
109
+ /* ── Placeholder HTML ─────────────────────────────── */
110
+
111
+ function _skeletonHtml() {
112
+ return '<div class="vd-skeleton-card" style="position:relative;min-height:200px;padding:2rem;overflow:hidden;">'
113
+ + '<div class="vd-skeleton vd-skeleton-heading-lg" style="margin-bottom:1.5rem;"></div>'
114
+ + '<div class="vd-skeleton vd-skeleton-paragraph">'
115
+ + '<div class="vd-skeleton vd-skeleton-text"></div>'
116
+ + '<div class="vd-skeleton vd-skeleton-text"></div>'
117
+ + '<div class="vd-skeleton vd-skeleton-text"></div></div>'
118
+ + '<div class="vd-dynamic-loader" style="position:absolute;inset:0;">'
119
+ + '<div class="vd-dynamic-loader-grid">'
120
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-success" style="animation-delay:0s;"></div>'
121
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-warning" style="animation-delay:-0.15s;"></div>'
122
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-error" style="animation-delay:-0.3s;"></div>'
123
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-info" style="animation-delay:-0.45s;"></div></div>'
124
+ + '<span class="vd-dynamic-loader-text">Loading…</span></div></div>';
125
+ }
126
+
127
+ function _spinnerHtml() {
128
+ return '<div class="vd-dynamic-loader" style="min-height:180px;display:flex;align-items:center;justify-content:center;">'
129
+ + '<div class="vd-dynamic-loader-grid">'
130
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-success" style="animation-delay:0s;"></div>'
131
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-warning" style="animation-delay:-0.15s;"></div>'
132
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-error" style="animation-delay:-0.3s;"></div>'
133
+ + '<div class="vd-spinner vd-spinner-sm vd-spinner-info" style="animation-delay:-0.45s;"></div></div>'
134
+ + '<span class="vd-dynamic-loader-text">Loading…</span></div>';
135
+ }
136
+
137
+ function _resolvePlaceholder(placeholder) {
138
+ if (!placeholder || placeholder === 'skeleton') return _skeletonHtml();
139
+ if (placeholder === 'spinner') return _spinnerHtml();
140
+ // Caller-supplied HTML string
141
+ return placeholder;
142
+ }
143
+
144
+ /* ── Dispatch helper ──────────────────────────────── */
145
+
146
+ function _dispatch(el, eventName, detail) {
147
+ el.dispatchEvent(new CustomEvent(eventName, { bubbles: true, detail: detail || {} }));
148
+ }
149
+
150
+ /* ── VanduoLazyLoad ──────────────────────────────── */
151
+
152
+ const VanduoLazyLoad = {
153
+
154
+ /* ─────────────────────────────────────────────────
155
+ * LOW-LEVEL API
156
+ * ───────────────────────────────────────────────── */
157
+
158
+ /**
159
+ * Observe an element. `callback` is invoked once when the element
160
+ * enters the viewport, then the element is automatically unobserved.
161
+ *
162
+ * @param {Element} element
163
+ * @param {function(Element): void} callback
164
+ * @param {{ threshold?: number, rootMargin?: string }} [options]
165
+ */
166
+ observe: function (element, callback, options) {
167
+ if (!(element instanceof Element)) {
168
+ console.warn('[VanduoLazyLoad] observe() requires a DOM Element.');
169
+ return;
170
+ }
171
+ if (typeof callback !== 'function') {
172
+ console.warn('[VanduoLazyLoad] observe() requires a callback function.');
173
+ return;
174
+ }
175
+ // Already observed — ignore
176
+ if (_observerMap.has(element)) return;
177
+
178
+ const threshold = (options && options.threshold != null) ? options.threshold : 0;
179
+ const rootMargin = (options && options.rootMargin) ? options.rootMargin : '0px';
180
+
181
+ const observer = new IntersectionObserver(function (entries, obs) {
182
+ entries.forEach(function (entry) {
183
+ if (entry.isIntersecting) {
184
+ obs.unobserve(entry.target);
185
+ _observerMap.delete(entry.target);
186
+ try {
187
+ callback(entry.target);
188
+ } catch (e) {
189
+ console.error('[VanduoLazyLoad] Callback threw:', e);
190
+ }
191
+ }
192
+ });
193
+ }, { threshold: threshold, rootMargin: rootMargin });
194
+
195
+ _observerMap.set(element, observer);
196
+ observer.observe(element);
197
+ },
198
+
199
+ /**
200
+ * Stop observing an element that was previously passed to observe().
201
+ * @param {Element} element
202
+ */
203
+ unobserve: function (element) {
204
+ const observer = _observerMap.get(element);
205
+ if (observer) {
206
+ observer.unobserve(element);
207
+ _observerMap.delete(element);
208
+ }
209
+ },
210
+
211
+ /**
212
+ * Stop observing ALL currently observed elements.
213
+ */
214
+ unobserveAll: function () {
215
+ _observerMap.forEach(function (observer, element) {
216
+ observer.unobserve(element);
217
+ });
218
+ _observerMap.clear();
219
+ },
220
+
221
+ /* ─────────────────────────────────────────────────
222
+ * HIGH-LEVEL API
223
+ * ───────────────────────────────────────────────── */
224
+
225
+ /**
226
+ * Fetch an HTML partial and inject it into `containerEl` when the
227
+ * container enters the viewport. A placeholder is shown immediately.
228
+ *
229
+ * @param {string} url URL of the HTML partial to fetch
230
+ * @param {Element} containerEl Target element whose content will be replaced
231
+ * @param {{
232
+ * placeholder?: 'skeleton'|'spinner'|string,
233
+ * threshold?: number,
234
+ * rootMargin?: string,
235
+ * onLoaded?: function(Element): void,
236
+ * onError?: function(Error): void
237
+ * }} [options]
238
+ */
239
+ loadSection: function (url, containerEl, options) {
240
+ if (typeof url !== 'string' || !url) {
241
+ console.warn('[VanduoLazyLoad] loadSection() requires a non-empty URL string.');
242
+ return;
243
+ }
244
+ if (!(containerEl instanceof Element)) {
245
+ console.warn('[VanduoLazyLoad] loadSection() requires a DOM Element as containerEl.');
246
+ return;
247
+ }
248
+ // Reject cross-origin URLs to prevent SSRF-style fetch abuse
249
+ if (!_isSafeUrl(url)) {
250
+ console.error('[VanduoLazyLoad] loadSection() blocked cross-origin URL:', url);
251
+ return;
252
+ }
253
+
254
+ const opts = options || {};
255
+ // Placeholders are known-safe static HTML strings built internally
256
+ const placeholderHtml = _resolvePlaceholder(opts.placeholder);
257
+
258
+ // Use _safeInjectHtml instead of innerHTML to prevent XSS from caller-supplied placeholders
259
+ _safeInjectHtml(containerEl, placeholderHtml);
260
+ _dispatch(containerEl, 'lazysection:loading', { url: url });
261
+
262
+ // Fetch when visible
263
+ this.observe(containerEl, function () {
264
+ const controller = new window.AbortController();
265
+ const timeoutId = setTimeout(function () { controller.abort(); }, 10000);
266
+
267
+ window.fetch(url, { signal: controller.signal })
268
+ .then(function (res) {
269
+ clearTimeout(timeoutId);
270
+ if (!res.ok) throw new Error('HTTP ' + res.status);
271
+ return res.text();
272
+ })
273
+ .then(function (html) {
274
+ // Use DOMParser to parse fetched content safely, avoiding
275
+ // raw innerHTML assignment of externally-sourced strings
276
+ _safeInjectHtml(containerEl, html);
277
+ _dispatch(containerEl, 'lazysection:loaded', { url: url });
278
+ // Re-init Vanduo components inside the new content
279
+ if (typeof window.Vanduo !== 'undefined') {
280
+ window.Vanduo.init();
281
+ }
282
+ if (typeof opts.onLoaded === 'function') {
283
+ opts.onLoaded(containerEl);
284
+ }
285
+ })
286
+ .catch(function (err) {
287
+ // Build error node via DOM APIs — no dynamic HTML strings
288
+ const alertEl = document.createElement('div');
289
+ alertEl.className = 'vd-alert vd-alert-error';
290
+ alertEl.setAttribute('role', 'alert');
291
+ const msgEl = document.createElement('span');
292
+ msgEl.textContent = 'Failed to load content. ';
293
+ const detailEl = document.createElement('small');
294
+ detailEl.style.opacity = '0.7';
295
+ detailEl.textContent = err.message;
296
+ alertEl.appendChild(msgEl);
297
+ alertEl.appendChild(detailEl);
298
+ while (containerEl.firstChild) {
299
+ containerEl.removeChild(containerEl.firstChild);
300
+ }
301
+ containerEl.appendChild(alertEl);
302
+ _dispatch(containerEl, 'lazysection:error', { url: url, error: err });
303
+ console.error('[VanduoLazyLoad] loadSection failed:', err);
304
+ if (typeof opts.onError === 'function') {
305
+ opts.onError(err);
306
+ }
307
+ });
308
+ }, { threshold: opts.threshold, rootMargin: opts.rootMargin });
309
+ },
310
+
311
+ /* ─────────────────────────────────────────────────
312
+ * ATTRIBUTE-DRIVEN INIT
313
+ * ───────────────────────────────────────────────── */
314
+
315
+ /**
316
+ * Scan the DOM for [data-vd-lazy] elements and wire them up.
317
+ * Safe to call multiple times — already-observed elements are skipped.
318
+ */
319
+ init: function () {
320
+ const self = this;
321
+ const elements = document.querySelectorAll('[data-vd-lazy]');
322
+ elements.forEach(function (el) {
323
+ // Skip already-observed or already-loaded elements
324
+ if (_observerMap.has(el) || el.dataset.vdLazyState === 'loading' || el.dataset.vdLazyState === 'loaded') return;
325
+
326
+ const url = el.getAttribute('data-vd-lazy');
327
+ if (!url) return;
328
+
329
+ el.dataset.vdLazyState = 'loading';
330
+ const placeholder = el.getAttribute('data-vd-lazy-placeholder') || 'skeleton';
331
+
332
+ self.loadSection(url, el, {
333
+ placeholder: placeholder,
334
+ onLoaded: function () {
335
+ el.dataset.vdLazyState = 'loaded';
336
+ },
337
+ onError: function () {
338
+ el.dataset.vdLazyState = 'error';
339
+ }
340
+ });
341
+ });
342
+ }
343
+ };
344
+
345
+ /* ── Register with Vanduo ─────────────────────────── */
346
+ if (typeof window.Vanduo !== 'undefined') {
347
+ window.Vanduo.register('LazyLoad', VanduoLazyLoad);
348
+ }
349
+
350
+ /* ── Global convenience alias ─────────────────────── */
351
+ window.VanduoLazyLoad = VanduoLazyLoad;
352
+
353
+ })();
@@ -483,28 +483,44 @@
483
483
  * Generate panel HTML
484
484
  */
485
485
  getPanelHTML: function () {
486
+ const esc = typeof escapeHtml === 'function'
487
+ ? escapeHtml
488
+ : function (text) {
489
+ const div = document.createElement('div');
490
+ div.textContent = String(text ?? '');
491
+ return div.innerHTML;
492
+ };
493
+ const safeColor = function (value) {
494
+ const normalized = String(value ?? '').trim();
495
+ // Allow common color formats used by palette values.
496
+ if (/^(#[0-9a-fA-F]{3,8}|rgb[a]?\([^)]{1,60}\)|hsl[a]?\([^)]{1,60}\)|var\(--[a-zA-Z0-9_-]{1,40}\))$/.test(normalized)) {
497
+ return normalized;
498
+ }
499
+ return '#000000';
500
+ };
501
+
486
502
  // Generate primary color swatches
487
503
  let primarySwatches = '';
488
504
  for (const [key, value] of Object.entries(this.PRIMARY_COLORS)) {
489
- primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? ' is-active' : ''}" data-color="${key}" style="--swatch-color: ${value.color}" title="${value.name}"></button>`;
505
+ primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? ' is-active' : ''}" data-color="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"></button>`;
490
506
  }
491
507
 
492
508
  // Generate neutral color swatches
493
509
  let neutralSwatches = '';
494
510
  for (const [key, value] of Object.entries(this.NEUTRAL_COLORS)) {
495
- neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? ' is-active' : ''}" data-neutral="${key}" style="--swatch-color: ${value.color}" title="${value.name}"><span>${value.name}</span></button>`;
511
+ neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? ' is-active' : ''}" data-neutral="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"><span>${esc(value.name)}</span></button>`;
496
512
  }
497
513
 
498
514
  // Generate radius buttons
499
515
  let radiusButtons = '';
500
516
  this.RADIUS_OPTIONS.forEach(r => {
501
- radiusButtons += `<button class="tc-radius-btn${r === this.state.radius ? ' is-active' : ''}" data-radius="${r}">${r}</button>`;
517
+ radiusButtons += `<button class="tc-radius-btn${r === this.state.radius ? ' is-active' : ''}" data-radius="${esc(r)}">${esc(r)}</button>`;
502
518
  });
503
519
 
504
520
  // Generate font options
505
521
  let fontOptions = '';
506
522
  for (const [key, value] of Object.entries(this.FONT_OPTIONS)) {
507
- fontOptions += `<option value="${key}"${key === this.state.font ? ' selected' : ''}>${value.name}</option>`;
523
+ fontOptions += `<option value="${esc(key)}"${key === this.state.font ? ' selected' : ''}>${esc(value.name)}</option>`;
508
524
  }
509
525
 
510
526
  // Generate mode buttons
@@ -23,7 +23,7 @@
23
23
  return sanitizeHtml(input);
24
24
  }
25
25
  // Fallback: strip all HTML
26
- var div = document.createElement('div');
26
+ const div = document.createElement('div');
27
27
  div.textContent = input || '';
28
28
  return div.innerHTML;
29
29
  },
package/js/index.js CHANGED
@@ -45,6 +45,7 @@ import './components/toast.js';
45
45
  import './components/tooltips.js';
46
46
  import './components/doc-search.js';
47
47
  import './components/draggable.js';
48
+ import './components/lazy-load.js';
48
49
 
49
50
  // Re-export for ESM / CJS consumers
50
51
  const Vanduo = window.Vanduo;
@@ -1,3 +1,5 @@
1
+ 'use strict';
2
+
1
3
  /**
2
4
  * Vanduo Framework - Utility Helpers
3
5
  * Common utility functions used across the framework
@@ -86,7 +88,11 @@ function on(target, event, handlerOrSelector, handler) {
86
88
  element.addEventListener(event, function (e) {
87
89
  const delegateTarget = e.target.closest(handlerOrSelector);
88
90
  if (delegateTarget && element.contains(delegateTarget)) {
89
- handler.call(delegateTarget, e);
91
+ try {
92
+ handler.call(delegateTarget, e);
93
+ } catch (error) {
94
+ console.warn('[Vanduo Helpers] Delegated handler error:', error);
95
+ }
90
96
  }
91
97
  });
92
98
  }
@@ -238,7 +244,7 @@ function getPosition(element) {
238
244
  */
239
245
  function escapeHtml(str) {
240
246
  if (!str) return '';
241
- var div = document.createElement('div');
247
+ const div = document.createElement('div');
242
248
  div.appendChild(document.createTextNode(str));
243
249
  return div.innerHTML;
244
250
  }
@@ -252,24 +258,30 @@ function escapeHtml(str) {
252
258
  */
253
259
  function sanitizeHtml(input) {
254
260
  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'];
261
+ let doc;
262
+ try {
263
+ doc = new DOMParser().parseFromString(input, 'text/html');
264
+ } catch (_error) {
265
+ // Fail closed to plain escaped text if parser is unavailable/fails.
266
+ return escapeHtml(input);
267
+ }
268
+ const allowed = ['B', 'STRONG', 'I', 'EM', 'BR', 'A', 'SPAN', 'U', 'SVG', 'PATH', 'LINE', 'CIRCLE', 'POLYLINE', 'RECT', 'G'];
257
269
 
258
- var sanitizeNode = function (node) {
259
- var children = Array.from(node.childNodes);
270
+ const sanitizeNode = function (node) {
271
+ const children = Array.from(node.childNodes);
260
272
  children.forEach(function (child) {
261
273
  if (child.nodeType === Node.TEXT_NODE) return;
262
274
 
263
275
  if (!allowed.includes(child.nodeName)) {
264
- var text = document.createTextNode(child.textContent);
276
+ const text = document.createTextNode(child.textContent);
265
277
  node.replaceChild(text, child);
266
278
  return;
267
279
  }
268
280
 
269
281
  if (child.nodeName === 'A') {
270
- var href = child.getAttribute('href') || '';
282
+ const href = child.getAttribute('href') || '';
271
283
  try {
272
- var url = new URL(href, location.href);
284
+ const url = new URL(href, location.href);
273
285
  if (!['http:', 'https:', 'mailto:'].includes(url.protocol)) {
274
286
  child.removeAttribute('href');
275
287
  }
@@ -280,17 +292,17 @@ function sanitizeHtml(input) {
280
292
  child.removeAttribute('rel');
281
293
  } else if (child.nodeName === 'SVG' || child.closest && child.closest('svg')) {
282
294
  // Allow safe SVG presentation attributes only
283
- var safeSvgAttrs = ['xmlns', 'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width',
295
+ const safeSvgAttrs = ['xmlns', 'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width',
284
296
  'stroke-linecap', 'stroke-linejoin', 'd', 'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2', 'points',
285
297
  'transform', 'class'];
286
- var attrs = Array.from(child.attributes || []);
298
+ const attrs = Array.from(child.attributes || []);
287
299
  attrs.forEach(function (a) {
288
300
  if (!safeSvgAttrs.includes(a.name)) {
289
301
  child.removeAttribute(a.name);
290
302
  }
291
303
  });
292
304
  } else {
293
- var otherAttrs = Array.from(child.attributes || []);
305
+ const otherAttrs = Array.from(child.attributes || []);
294
306
  otherAttrs.forEach(function (a) { child.removeAttribute(a.name); });
295
307
  }
296
308
 
package/js/vanduo.js CHANGED
@@ -1,23 +1,23 @@
1
1
  /**
2
2
  * Vanduo Framework - Main JavaScript File
3
- * v1.1.6
3
+ * v1.2.1
4
4
  */
5
5
 
6
- (function() {
6
+ (function () {
7
7
  'use strict';
8
8
 
9
9
  /**
10
10
  * Vanduo Framework Object
11
11
  */
12
12
  const Vanduo = {
13
- version: '1.1.6',
13
+ version: '1.2.1',
14
14
  components: {},
15
15
 
16
16
  /**
17
17
  * Initialize framework
18
18
  * Call this after DOM is ready and all components are loaded
19
19
  */
20
- init: function() {
20
+ init: function () {
21
21
  // Initialize components when DOM is ready
22
22
  if (typeof ready !== 'undefined') {
23
23
  ready(() => {
@@ -38,7 +38,7 @@
38
38
  /**
39
39
  * Initialize all components
40
40
  */
41
- initComponents: function() {
41
+ initComponents: function () {
42
42
  // Initialize all registered components
43
43
  Object.keys(this.components).forEach((name) => {
44
44
  const component = this.components[name];
@@ -51,7 +51,7 @@
51
51
  }
52
52
  });
53
53
 
54
- console.log('Vanduo Framework v1.1.6 initialized');
54
+ console.log('Vanduo Framework v1.2.1 initialized');
55
55
  },
56
56
 
57
57
  /**
@@ -59,7 +59,7 @@
59
59
  * @param {string} name - Component name
60
60
  * @param {Object} component - Component object with init method
61
61
  */
62
- register: function(name, component) {
62
+ register: function (name, component) {
63
63
  this.components[name] = component;
64
64
  // Note: Components are NOT auto-initialized on registration
65
65
  // Call Vanduo.init() explicitly after all components are registered
@@ -69,8 +69,8 @@
69
69
  * Re-initialize a component (useful after dynamic DOM changes)
70
70
  * @param {string} name - Component name
71
71
  */
72
- reinit: function(name) {
73
- var component = this.components[name];
72
+ reinit: function (name) {
73
+ const component = this.components[name];
74
74
  if (component && component.init && typeof component.init === 'function') {
75
75
  try {
76
76
  component.init();
@@ -84,11 +84,11 @@
84
84
  * Destroy all component instances and clean up event listeners
85
85
  * Uses lifecycle manager for memory leak prevention
86
86
  */
87
- destroyAll: function() {
87
+ destroyAll: function () {
88
88
  // First, destroy components that have their own destroyAll
89
- var names = Object.keys(this.components);
90
- for (var i = 0; i < names.length; i++) {
91
- var component = this.components[names[i]];
89
+ const names = Object.keys(this.components);
90
+ for (let i = 0; i < names.length; i++) {
91
+ const component = this.components[names[i]];
92
92
  if (component && component.destroyAll && typeof component.destroyAll === 'function') {
93
93
  try {
94
94
  component.destroyAll();
@@ -109,7 +109,7 @@
109
109
  * @param {string} name - Component name
110
110
  * @returns {Object|null}
111
111
  */
112
- getComponent: function(name) {
112
+ getComponent: function (name) {
113
113
  return this.components[name] || null;
114
114
  }
115
115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanduo-framework",
3
- "version": "1.1.8",
3
+ "version": "1.2.1",
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",
@@ -42,10 +42,10 @@
42
42
  "@eslint/js": "^10.0.1",
43
43
  "@playwright/test": "^1.58.2",
44
44
  "esbuild": "^0.27.3",
45
- "eslint": "^10.0.1",
45
+ "eslint": "^10.0.2",
46
46
  "husky": "^9.1.7",
47
47
  "lightningcss": "^1.31.1",
48
- "stylelint": "^17.3.0",
48
+ "stylelint": "^17.4.0",
49
49
  "stylelint-config-standard": "^40.0.0"
50
50
  },
51
51
  "engines": {