@vanduo-oss/framework 1.3.0 → 1.3.2
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 +13 -12
- package/css/components/music-player.css +578 -0
- package/css/components/navbar.css +5 -0
- package/css/vanduo.css +1 -0
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +729 -44
- package/dist/vanduo.cjs.js.map +3 -3
- package/dist/vanduo.cjs.min.js +5 -5
- package/dist/vanduo.cjs.min.js.map +4 -4
- package/dist/vanduo.css +512 -1
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +729 -44
- package/dist/vanduo.esm.js.map +3 -3
- package/dist/vanduo.esm.min.js +5 -5
- package/dist/vanduo.esm.min.js.map +4 -4
- package/dist/vanduo.js +729 -44
- 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 +5 -5
- package/dist/vanduo.min.js.map +4 -4
- package/js/components/code-snippet.js +5 -4
- package/js/components/dropdown.js +9 -9
- package/js/components/image-box.js +7 -1
- package/js/components/modals.js +18 -10
- package/js/components/music-player.js +848 -0
- package/js/components/navbar.js +3 -3
- package/js/components/select.js +15 -13
- package/js/components/suggest.js +14 -1
- package/js/components/theme-customizer.js +0 -1
- package/js/components/validate.js +14 -3
- package/js/index.js +1 -0
- package/js/utils/helpers.js +7 -3
- package/package.json +2 -2
package/js/components/navbar.js
CHANGED
|
@@ -203,8 +203,8 @@
|
|
|
203
203
|
overlay.classList.add('is-active');
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
// Prevent body scroll when menu is open
|
|
207
|
-
document.body.
|
|
206
|
+
// Prevent body scroll when menu is open (use class to avoid conflicts with modals)
|
|
207
|
+
document.body.classList.add('body-navbar-open');
|
|
208
208
|
|
|
209
209
|
// Set ARIA attributes
|
|
210
210
|
toggle.setAttribute('aria-expanded', 'true');
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Restore body scroll
|
|
230
|
-
document.body.
|
|
230
|
+
document.body.classList.remove('body-navbar-open');
|
|
231
231
|
|
|
232
232
|
// Close all dropdown menus
|
|
233
233
|
const dropdownMenus = menu.querySelectorAll('.vd-navbar-dropdown-menu.is-open');
|
package/js/components/select.js
CHANGED
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
const Select = {
|
|
13
13
|
// Store initialized selects and their cleanup functions
|
|
14
14
|
instances: new Map(),
|
|
15
|
-
// Typeahead state
|
|
16
|
-
_typeaheadBuffer: '',
|
|
17
|
-
_typeaheadTimer: null,
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* Initialize select components
|
|
@@ -123,7 +120,7 @@
|
|
|
123
120
|
select.addEventListener('change', changeHandler);
|
|
124
121
|
cleanupFunctions.push(() => select.removeEventListener('change', changeHandler));
|
|
125
122
|
|
|
126
|
-
this.instances.set(select, { wrapper, button, dropdown, cleanup: cleanupFunctions });
|
|
123
|
+
this.instances.set(select, { wrapper, button, dropdown, cleanup: cleanupFunctions, typeaheadBuffer: '', typeaheadTimer: null });
|
|
127
124
|
},
|
|
128
125
|
|
|
129
126
|
/**
|
|
@@ -233,7 +230,7 @@
|
|
|
233
230
|
* @param {HTMLElement} dropdown - Dropdown container
|
|
234
231
|
*/
|
|
235
232
|
updateSelectedOptions: function (select, dropdown) {
|
|
236
|
-
const options = dropdown.querySelectorAll('.
|
|
233
|
+
const options = dropdown.querySelectorAll('.custom-select-option');
|
|
237
234
|
const selectedValues = Array.from(select.selectedOptions).map(opt => opt.value);
|
|
238
235
|
|
|
239
236
|
options.forEach(optionEl => {
|
|
@@ -273,7 +270,7 @@
|
|
|
273
270
|
button.setAttribute('aria-expanded', 'true');
|
|
274
271
|
|
|
275
272
|
// Focus first option
|
|
276
|
-
const firstOption = dropdown.querySelector('.
|
|
273
|
+
const firstOption = dropdown.querySelector('.custom-select-option:not(.is-disabled)');
|
|
277
274
|
if (firstOption) {
|
|
278
275
|
firstOption.focus();
|
|
279
276
|
}
|
|
@@ -298,7 +295,7 @@
|
|
|
298
295
|
*/
|
|
299
296
|
handleKeydown: function (e, select, button, dropdown) {
|
|
300
297
|
const isOpen = dropdown.classList.contains('is-open');
|
|
301
|
-
const options = Array.from(dropdown.querySelectorAll('.
|
|
298
|
+
const options = Array.from(dropdown.querySelectorAll('.custom-select-option:not(.is-disabled)'));
|
|
302
299
|
const currentIndex = options.findIndex(opt => opt === document.activeElement);
|
|
303
300
|
|
|
304
301
|
switch (e.key) {
|
|
@@ -357,18 +354,21 @@
|
|
|
357
354
|
default:
|
|
358
355
|
// Typeahead: jump to matching option when typing printable characters
|
|
359
356
|
if (isOpen && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
360
|
-
|
|
361
|
-
|
|
357
|
+
// Per-instance typeahead state to avoid cross-instance corruption
|
|
358
|
+
const instance = this.instances.get(select);
|
|
359
|
+
if (!instance) break;
|
|
360
|
+
clearTimeout(instance.typeaheadTimer);
|
|
361
|
+
instance.typeaheadBuffer += e.key.toLowerCase();
|
|
362
362
|
|
|
363
363
|
const match = options.find(opt =>
|
|
364
|
-
opt.textContent.trim().toLowerCase().startsWith(
|
|
364
|
+
opt.textContent.trim().toLowerCase().startsWith(instance.typeaheadBuffer)
|
|
365
365
|
);
|
|
366
366
|
if (match) {
|
|
367
367
|
match.focus();
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
|
|
370
|
+
instance.typeaheadTimer = setTimeout(() => {
|
|
371
|
+
instance.typeaheadBuffer = '';
|
|
372
372
|
}, 500);
|
|
373
373
|
}
|
|
374
374
|
break;
|
|
@@ -403,7 +403,9 @@
|
|
|
403
403
|
if (element.id) {
|
|
404
404
|
return element.id;
|
|
405
405
|
}
|
|
406
|
-
|
|
406
|
+
const id = 'select-' + Math.random().toString(36).substr(2, 9);
|
|
407
|
+
element.id = id;
|
|
408
|
+
return id;
|
|
407
409
|
},
|
|
408
410
|
|
|
409
411
|
/**
|
package/js/components/suggest.js
CHANGED
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
(function () {
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Escape HTML entities to prevent XSS when inserting into innerHTML
|
|
11
|
+
* @param {string} text
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
function _escapeHtml(text) {
|
|
15
|
+
const div = document.createElement('div');
|
|
16
|
+
div.textContent = text;
|
|
17
|
+
return div.innerHTML;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
const Suggest = {
|
|
10
21
|
instances: new Map(),
|
|
11
22
|
|
|
@@ -77,8 +88,10 @@
|
|
|
77
88
|
|
|
78
89
|
const text = typeof item === 'object' ? (item.label || item.text || String(item)) : String(item);
|
|
79
90
|
if (query) {
|
|
91
|
+
// Escape HTML first to prevent XSS, then highlight matches in the safe string
|
|
92
|
+
const escaped = _escapeHtml(text);
|
|
80
93
|
const re = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
|
|
81
|
-
li.innerHTML =
|
|
94
|
+
li.innerHTML = escaped.replace(re, '<span class="vd-suggest-match">$1</span>');
|
|
82
95
|
} else {
|
|
83
96
|
li.textContent = text;
|
|
84
97
|
}
|
|
@@ -714,7 +714,6 @@
|
|
|
714
714
|
this.applyNeutral(this.DEFAULTS.NEUTRAL);
|
|
715
715
|
this.applyRadius(this.DEFAULTS.RADIUS);
|
|
716
716
|
this.applyFont(this.DEFAULTS.FONT);
|
|
717
|
-
this.applyTheme(this.DEFAULTS.THEME);
|
|
718
717
|
this.updateUI();
|
|
719
718
|
|
|
720
719
|
this.dispatchEvent('reset', { state: { ...this.state } });
|
|
@@ -18,10 +18,21 @@
|
|
|
18
18
|
max: (value, param) => value.length <= parseInt(param, 10),
|
|
19
19
|
minVal: (value, param) => parseFloat(value) >= parseFloat(param),
|
|
20
20
|
maxVal: (value, param) => parseFloat(value) <= parseFloat(param),
|
|
21
|
-
pattern: (value, param) => {
|
|
21
|
+
pattern: (value, param) => {
|
|
22
|
+
try {
|
|
23
|
+
// Cap regex length to prevent ReDoS from excessively complex patterns
|
|
24
|
+
if (param.length > 100) return false;
|
|
25
|
+
return new RegExp(param).test(value);
|
|
26
|
+
} catch (_e) { return false; }
|
|
27
|
+
},
|
|
22
28
|
match: (value, param) => {
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
try {
|
|
30
|
+
const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(param) : param;
|
|
31
|
+
const other = document.querySelector('[name="' + escaped + '"]');
|
|
32
|
+
return other ? value === other.value : false;
|
|
33
|
+
} catch (_e) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
25
36
|
}
|
|
26
37
|
},
|
|
27
38
|
|
package/js/index.js
CHANGED
package/js/utils/helpers.js
CHANGED
|
@@ -74,6 +74,7 @@ function safeStorageSet(key, value) {
|
|
|
74
74
|
* @param {string} event - Event type
|
|
75
75
|
* @param {string|Function} handlerOrSelector - Event handler or selector for delegation
|
|
76
76
|
* @param {Function} handler - Event handler (if using delegation)
|
|
77
|
+
* @returns {Function|undefined} The actual bound handler (use this with off() to remove delegation listeners)
|
|
77
78
|
*/
|
|
78
79
|
function on(target, event, handlerOrSelector, handler) {
|
|
79
80
|
const element = typeof target === 'string' ? $(target) : target;
|
|
@@ -83,9 +84,10 @@ function on(target, event, handlerOrSelector, handler) {
|
|
|
83
84
|
if (typeof handlerOrSelector === 'function') {
|
|
84
85
|
// Direct event binding
|
|
85
86
|
element.addEventListener(event, handlerOrSelector);
|
|
87
|
+
return handlerOrSelector;
|
|
86
88
|
} else {
|
|
87
|
-
// Event delegation
|
|
88
|
-
|
|
89
|
+
// Event delegation — return the wrapper so callers can remove it via off()
|
|
90
|
+
const wrapper = function (e) {
|
|
89
91
|
const delegateTarget = e.target.closest(handlerOrSelector);
|
|
90
92
|
if (delegateTarget && element.contains(delegateTarget)) {
|
|
91
93
|
try {
|
|
@@ -94,7 +96,9 @@ function on(target, event, handlerOrSelector, handler) {
|
|
|
94
96
|
console.warn('[Vanduo Helpers] Delegated handler error:', error);
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
}
|
|
99
|
+
};
|
|
100
|
+
element.addEventListener(event, wrapper);
|
|
101
|
+
return wrapper;
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanduo-oss/framework",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
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.0.3",
|
|
46
46
|
"husky": "^9.1.7",
|
|
47
47
|
"lightningcss": "^1.32.0",
|
|
48
|
-
"stylelint": "^17.
|
|
48
|
+
"stylelint": "^17.5.0",
|
|
49
49
|
"stylelint-config-standard": "^40.0.0"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|