@vanduo-oss/framework 1.4.2 → 1.4.4
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 +15 -3
- package/css/components/theme-switcher.css +129 -0
- package/css/vanduo.css +1 -1
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +328 -1098
- package/dist/vanduo.cjs.js.map +3 -3
- package/dist/vanduo.cjs.min.js +8 -8
- package/dist/vanduo.cjs.min.js.map +4 -4
- package/dist/vanduo.css +117 -690
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +328 -1098
- package/dist/vanduo.esm.js.map +3 -3
- package/dist/vanduo.esm.min.js +8 -8
- package/dist/vanduo.esm.min.js.map +4 -4
- package/dist/vanduo.js +328 -1098
- 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 +8 -8
- package/dist/vanduo.min.js.map +4 -4
- package/js/components/code-snippet.js +87 -24
- package/js/components/datepicker.js +27 -1
- package/js/components/suggest.js +18 -15
- package/js/components/theme-switcher.js +290 -29
- package/js/index.js +0 -1
- package/package.json +5 -5
- package/css/components/music-player.css +0 -798
- package/js/components/music-player.js +0 -1294
|
@@ -69,9 +69,10 @@
|
|
|
69
69
|
const snippets = this.queryWithin(root, '.vd-code-snippet');
|
|
70
70
|
|
|
71
71
|
snippets.forEach(snippet => {
|
|
72
|
-
if (
|
|
73
|
-
|
|
72
|
+
if (snippet.dataset.initialized === 'true') {
|
|
73
|
+
return;
|
|
74
74
|
}
|
|
75
|
+
this.initSnippet(snippet);
|
|
75
76
|
});
|
|
76
77
|
},
|
|
77
78
|
|
|
@@ -80,7 +81,9 @@
|
|
|
80
81
|
* @param {HTMLElement} snippet - Code snippet container element
|
|
81
82
|
*/
|
|
82
83
|
initSnippet: function (snippet) {
|
|
83
|
-
snippet.dataset.initialized
|
|
84
|
+
if (snippet.dataset.initialized === 'true') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
84
87
|
snippet._codeSnippetCleanup = [];
|
|
85
88
|
|
|
86
89
|
// Handle collapsible toggle
|
|
@@ -121,6 +124,8 @@
|
|
|
121
124
|
lineNumberPanes.forEach(pane => {
|
|
122
125
|
this.addLineNumbers(pane);
|
|
123
126
|
});
|
|
127
|
+
|
|
128
|
+
snippet.dataset.initialized = 'true';
|
|
124
129
|
},
|
|
125
130
|
|
|
126
131
|
/**
|
|
@@ -132,14 +137,15 @@
|
|
|
132
137
|
initCollapsible: function (snippet, toggle, content) {
|
|
133
138
|
// Set initial state
|
|
134
139
|
const isExpanded = snippet.dataset.expanded === 'true';
|
|
135
|
-
toggle.setAttribute('aria-expanded', isExpanded);
|
|
136
|
-
content.dataset.visible = isExpanded;
|
|
140
|
+
toggle.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
|
|
141
|
+
content.dataset.visible = isExpanded ? 'true' : 'false';
|
|
137
142
|
|
|
138
143
|
this.addListener(snippet, toggle, 'click', () => {
|
|
139
144
|
const expanded = snippet.dataset.expanded === 'true';
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
const nextExpanded = !expanded;
|
|
146
|
+
snippet.dataset.expanded = nextExpanded ? 'true' : 'false';
|
|
147
|
+
toggle.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
|
|
148
|
+
content.dataset.visible = nextExpanded ? 'true' : 'false';
|
|
143
149
|
|
|
144
150
|
// Extract HTML on first expand if needed
|
|
145
151
|
if (!expanded) {
|
|
@@ -566,19 +572,65 @@
|
|
|
566
572
|
* @returns {string} HTML with syntax highlighting spans
|
|
567
573
|
*/
|
|
568
574
|
highlightHtml: function (html) {
|
|
569
|
-
//
|
|
570
|
-
html = html.replace(/(<
|
|
575
|
+
// Comments first so tag highlighting does not treat them as elements.
|
|
576
|
+
html = html.replace(/(<!--)(.*?)(-->)/g, '<span class="code-comment">$1$2$3</span>');
|
|
571
577
|
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
html = html.replace(
|
|
578
|
+
// Only highlight inside escaped HTML tags. Build the highlighted tag from the
|
|
579
|
+
// original escaped source so regexes never re-process our injected spans.
|
|
580
|
+
html = html.replace(/<(?!--)([\s\S]*?)>/g, function (match, inner) {
|
|
581
|
+
const closingMatch = inner.match(/^(\/)([\w-]+)\s*$/);
|
|
582
|
+
if (closingMatch) {
|
|
583
|
+
return '<'
|
|
584
|
+
+ closingMatch[1]
|
|
585
|
+
+ '<span class="code-tag">' + closingMatch[2] + '</span>'
|
|
586
|
+
+ '>';
|
|
587
|
+
}
|
|
575
588
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
589
|
+
const selfClosingMatch = inner.match(/(\s*\/)\s*$/);
|
|
590
|
+
const selfClosingSuffix = selfClosingMatch ? selfClosingMatch[1] : '';
|
|
591
|
+
const tagSource = selfClosingMatch
|
|
592
|
+
? inner.slice(0, inner.length - selfClosingMatch[0].length)
|
|
593
|
+
: inner;
|
|
594
|
+
const openMatch = tagSource.match(/^([\w-]+)([\s\S]*)$/);
|
|
579
595
|
|
|
580
|
-
|
|
581
|
-
|
|
596
|
+
if (!openMatch) {
|
|
597
|
+
return match;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const tagName = openMatch[1];
|
|
601
|
+
let attrSource = openMatch[2];
|
|
602
|
+
|
|
603
|
+
attrSource = attrSource.replace(
|
|
604
|
+
/([\w:-]+)(\s*=\s*)(["'])([\s\S]*?)\3/g,
|
|
605
|
+
function (attrMatch, attrName, separator, quote, value) {
|
|
606
|
+
return '<span class="code-attr">' + attrName + '</span>'
|
|
607
|
+
+ separator
|
|
608
|
+
+ quote
|
|
609
|
+
+ '<span class="code-string">' + value + '</span>'
|
|
610
|
+
+ quote;
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
attrSource = attrSource.replace(
|
|
615
|
+
/([\w:-]+)(\s*=\s*)("|')([\s\S]*?)("|')/g,
|
|
616
|
+
function (attrMatch, attrName, separator, openQuote, value, closeQuote) {
|
|
617
|
+
if (openQuote !== closeQuote) {
|
|
618
|
+
return attrMatch;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return '<span class="code-attr">' + attrName + '</span>'
|
|
622
|
+
+ separator
|
|
623
|
+
+ openQuote
|
|
624
|
+
+ '<span class="code-string">' + value + '</span>'
|
|
625
|
+
+ closeQuote;
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
return '<<span class="code-tag">' + tagName + '</span>'
|
|
630
|
+
+ attrSource
|
|
631
|
+
+ selfClosingSuffix
|
|
632
|
+
+ '>';
|
|
633
|
+
});
|
|
582
634
|
|
|
583
635
|
return html;
|
|
584
636
|
},
|
|
@@ -613,6 +665,20 @@
|
|
|
613
665
|
* @returns {string} JS with syntax highlighting spans
|
|
614
666
|
*/
|
|
615
667
|
highlightJs: function (js) {
|
|
668
|
+
const protectedTokens = [];
|
|
669
|
+
const protectToken = (value, className) => {
|
|
670
|
+
const marker = String.fromCharCode(0xE000 + protectedTokens.length);
|
|
671
|
+
protectedTokens.push({ marker, html: '<span class="' + className + '">' + value + '</span>' });
|
|
672
|
+
return marker;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// Protect comments and strings before adding markup so later passes never
|
|
676
|
+
// re-highlight attributes inside the generated spans.
|
|
677
|
+
js = js.replace(
|
|
678
|
+
/\/\*[\s\S]*?\*\/|\/\/.*$|'(?:[^'\\]|\\.){0,10000}'|"(?:[^"\\]|\\.){0,10000}"|`(?:[^`\\]|\\.){0,10000}`/gm,
|
|
679
|
+
match => protectToken(match, match.startsWith('/') ? 'code-comment' : 'code-string')
|
|
680
|
+
);
|
|
681
|
+
|
|
616
682
|
// Highlight keywords
|
|
617
683
|
const keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'switch', 'case', 'break', 'continue', 'new', 'this', 'class', 'extends', 'import', 'export', 'default', 'async', 'await', 'try', 'catch', 'throw', 'typeof', 'instanceof'];
|
|
618
684
|
keywords.forEach(kw => {
|
|
@@ -620,18 +686,15 @@
|
|
|
620
686
|
js = js.replace(regex, '<span class="code-keyword">$1</span>');
|
|
621
687
|
});
|
|
622
688
|
|
|
623
|
-
// Highlight strings (limit to 10 000 chars to prevent polynomial backtracking)
|
|
624
|
-
js = js.replace(/('(?:[^'\\]|\\.){0,10000}'|"(?:[^"\\]|\\.){0,10000}"|`(?:[^`\\]|\\.){0,10000}`)/g, '<span class="code-string">$1</span>');
|
|
625
|
-
|
|
626
689
|
// Highlight numbers
|
|
627
690
|
js = js.replace(/\b(\d+\.?\d*)\b/g, '<span class="code-number">$1</span>');
|
|
628
691
|
|
|
629
692
|
// Highlight function calls
|
|
630
693
|
js = js.replace(/\b([\w]+)(\s*\()/g, '<span class="code-function">$1</span>$2');
|
|
631
694
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
695
|
+
protectedTokens.forEach(token => {
|
|
696
|
+
js = js.replace(token.marker, token.html);
|
|
697
|
+
});
|
|
635
698
|
|
|
636
699
|
return js;
|
|
637
700
|
},
|
|
@@ -181,6 +181,8 @@
|
|
|
181
181
|
let focusedDate = null;
|
|
182
182
|
/** Prevents focus() after close from immediately re-opening the popup */
|
|
183
183
|
let skipNextFocusOpen = false;
|
|
184
|
+
/** Ignore outside-click closes briefly after open (mobile click/focus ordering). */
|
|
185
|
+
let ignoreOutsideUntil = 0;
|
|
184
186
|
|
|
185
187
|
const isDisabled = (d) => {
|
|
186
188
|
if (minDate) {
|
|
@@ -562,7 +564,12 @@
|
|
|
562
564
|
positionPopup();
|
|
563
565
|
};
|
|
564
566
|
|
|
567
|
+
const markIgnoreOutside = () => {
|
|
568
|
+
ignoreOutsideUntil = Date.now() + 100;
|
|
569
|
+
};
|
|
570
|
+
|
|
565
571
|
const open = () => {
|
|
572
|
+
markIgnoreOutside();
|
|
566
573
|
viewMode = 'days';
|
|
567
574
|
if (selectedDate) {
|
|
568
575
|
viewYear = selectedDate.getFullYear();
|
|
@@ -600,8 +607,25 @@
|
|
|
600
607
|
}
|
|
601
608
|
open();
|
|
602
609
|
};
|
|
610
|
+
/** Mobile browsers may not focus the input before the click event (Playwright too). */
|
|
611
|
+
const clickHandler = () => {
|
|
612
|
+
if (!popup.classList.contains('is-open')) open();
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const isOpenAnchorTarget = (target) => {
|
|
616
|
+
if (!target || !(target instanceof Node)) return false;
|
|
617
|
+
if (target === input || input.contains(target) || popup.contains(target)) return true;
|
|
618
|
+
const inputId = input.id;
|
|
619
|
+
if (inputId) {
|
|
620
|
+
const label = document.querySelector('label[for="' + inputId.replace(/"/g, '\\"') + '"]');
|
|
621
|
+
if (label && (target === label || label.contains(target))) return true;
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
};
|
|
625
|
+
|
|
603
626
|
const outsideHandler = (e) => {
|
|
604
|
-
if (
|
|
627
|
+
if (Date.now() < ignoreOutsideUntil) return;
|
|
628
|
+
if (!isOpenAnchorTarget(e.target)) close();
|
|
605
629
|
};
|
|
606
630
|
const escHandler = (e) => {
|
|
607
631
|
if (e.key === 'Escape' && popup.classList.contains('is-open')) {
|
|
@@ -612,6 +636,7 @@
|
|
|
612
636
|
};
|
|
613
637
|
|
|
614
638
|
input.addEventListener('focus', focusHandler);
|
|
639
|
+
input.addEventListener('click', clickHandler);
|
|
615
640
|
document.addEventListener('click', outsideHandler, true);
|
|
616
641
|
document.addEventListener('keydown', escHandler);
|
|
617
642
|
popup.addEventListener('keydown', handleGridKeydown);
|
|
@@ -624,6 +649,7 @@
|
|
|
624
649
|
|
|
625
650
|
cleanup.push(
|
|
626
651
|
() => input.removeEventListener('focus', focusHandler),
|
|
652
|
+
() => input.removeEventListener('click', clickHandler),
|
|
627
653
|
() => document.removeEventListener('click', outsideHandler, true),
|
|
628
654
|
() => document.removeEventListener('keydown', escHandler),
|
|
629
655
|
() => popup.removeEventListener('keydown', handleGridKeydown),
|
package/js/components/suggest.js
CHANGED
|
@@ -6,17 +6,6 @@
|
|
|
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
|
-
|
|
20
9
|
/**
|
|
21
10
|
* Allow same-origin URLs by default, or an explicit allowlist of origins.
|
|
22
11
|
* @param {string} url
|
|
@@ -109,10 +98,24 @@
|
|
|
109
98
|
|
|
110
99
|
const text = typeof item === 'object' ? (item.label || item.text || String(item)) : String(item);
|
|
111
100
|
if (query) {
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
const lowerText = text.toLowerCase();
|
|
102
|
+
const lowerQuery = query.toLowerCase();
|
|
103
|
+
let start = 0;
|
|
104
|
+
let matchIndex = lowerText.indexOf(lowerQuery, start);
|
|
105
|
+
while (matchIndex !== -1) {
|
|
106
|
+
if (matchIndex > start) {
|
|
107
|
+
li.appendChild(document.createTextNode(text.slice(start, matchIndex)));
|
|
108
|
+
}
|
|
109
|
+
const matchSpan = document.createElement('span');
|
|
110
|
+
matchSpan.className = 'vd-suggest-match';
|
|
111
|
+
matchSpan.textContent = text.slice(matchIndex, matchIndex + query.length);
|
|
112
|
+
li.appendChild(matchSpan);
|
|
113
|
+
start = matchIndex + query.length;
|
|
114
|
+
matchIndex = lowerText.indexOf(lowerQuery, start);
|
|
115
|
+
}
|
|
116
|
+
if (start < text.length) {
|
|
117
|
+
li.appendChild(document.createTextNode(text.slice(start)));
|
|
118
|
+
}
|
|
116
119
|
} else {
|
|
117
120
|
li.textContent = text;
|
|
118
121
|
}
|