@vanduo-oss/framework 1.4.3 → 1.4.5
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 +21 -4
- package/css/components/draggable.css +4 -0
- package/css/components/forms.css +1 -0
- package/css/components/theme-switcher.css +129 -0
- package/css/vanduo.css +1 -0
- package/dist/build-info.json +3 -3
- package/dist/vanduo.cjs.js +244 -15
- package/dist/vanduo.cjs.js.map +2 -2
- package/dist/vanduo.cjs.min.js +6 -6
- package/dist/vanduo.cjs.min.js.map +3 -3
- package/dist/vanduo.css +122 -1
- package/dist/vanduo.css.map +1 -1
- package/dist/vanduo.esm.js +244 -15
- package/dist/vanduo.esm.js.map +2 -2
- package/dist/vanduo.esm.min.js +6 -6
- package/dist/vanduo.esm.min.js.map +3 -3
- package/dist/vanduo.js +244 -15
- package/dist/vanduo.js.map +2 -2
- package/dist/vanduo.min.css +2 -2
- package/dist/vanduo.min.css.map +1 -1
- package/dist/vanduo.min.js +6 -6
- package/dist/vanduo.min.js.map +3 -3
- package/js/components/code-snippet.js +14 -8
- package/js/components/theme-switcher.js +290 -29
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Vanduo Framework v1.4.
|
|
1
|
+
# Vanduo Framework v1.4.5
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="vanduo-banner.svg" alt="Vanduo Framework Banner" width="100%">
|
|
@@ -22,15 +22,27 @@ Vanduo is a zero-dependency UI framework built with HTML, CSS, and vanilla JavaS
|
|
|
22
22
|
- Strict design token API under `--vd-*`
|
|
23
23
|
- Built-in dark, light, and system theming
|
|
24
24
|
- Theme customizer with color, font, and radius controls
|
|
25
|
+
- Theme Switcher menu variant for icon-only light/dark/system selection in navbars
|
|
25
26
|
- Playwright-based browser coverage across Chromium, Firefox, and WebKit
|
|
26
27
|
|
|
28
|
+
## What's New in 1.4.5
|
|
29
|
+
|
|
30
|
+
- **Rounded-corner background fixes** — draggable surfaces and input-group addons use `background-clip: padding-box` so opaque fills no longer bleed past border arcs at large `data-radius` presets.
|
|
31
|
+
- **Background-clip audit** — Playwright `@audit` harness at `tests/audit/background-clip-audit.spec.ts` for regression checks.
|
|
32
|
+
- Normative API: [openspec/specs/draggable/spec.md](openspec/specs/draggable/spec.md).
|
|
33
|
+
|
|
34
|
+
## Previous: 1.4.4
|
|
35
|
+
|
|
36
|
+
- **Theme Switcher menu variant** — `.vd-theme-switcher[data-theme-ui="menu"]` opens an icon-only picker; option click applies the theme without cycling on toggle.
|
|
37
|
+
- Normative API: [openspec/specs/theme-switcher/spec.md](openspec/specs/theme-switcher/spec.md).
|
|
38
|
+
|
|
27
39
|
## Quick Start
|
|
28
40
|
|
|
29
41
|
### CDN
|
|
30
42
|
|
|
31
43
|
```html
|
|
32
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.4.
|
|
33
|
-
<script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.4.
|
|
44
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.4.5/dist/vanduo.min.css">
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.4.5/dist/vanduo.min.js"></script>
|
|
34
46
|
<script>
|
|
35
47
|
Vanduo.init();
|
|
36
48
|
</script>
|
|
@@ -114,6 +126,7 @@ framework/
|
|
|
114
126
|
├── dist/ # Built artifacts
|
|
115
127
|
├── tests/ # Playwright fixtures and specs
|
|
116
128
|
├── scripts/ # Build, verification, and inventory scripts
|
|
129
|
+
├── openspec/ # Spec-driven change proposals and component specs
|
|
117
130
|
└── docs/*.md # Release and architecture notes
|
|
118
131
|
```
|
|
119
132
|
|
|
@@ -129,11 +142,15 @@ pnpm test
|
|
|
129
142
|
pnpm run stats:css
|
|
130
143
|
```
|
|
131
144
|
|
|
145
|
+
CI runs **Chromium Desktop smoke tests on pull requests** and the **full cross-browser Playwright matrix on push to `main`** (see [`.github/workflows/tests.yml`](.github/workflows/tests.yml) and [QA-Automation-Strategy.md](QA-Automation-Strategy.md)).
|
|
146
|
+
|
|
132
147
|
## Release Notes
|
|
133
148
|
|
|
134
149
|
- Architecture notes: [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
135
150
|
- Token model: [TOKENS.md](TOKENS.md)
|
|
136
|
-
-
|
|
151
|
+
- Theme Switcher spec: [openspec/specs/theme-switcher/spec.md](openspec/specs/theme-switcher/spec.md)
|
|
152
|
+
- Draggable spec: [openspec/specs/draggable/spec.md](openspec/specs/draggable/spec.md)
|
|
153
|
+
- Changelog policy: [openspec/specs/changelog/spec.md](openspec/specs/changelog/spec.md)
|
|
137
154
|
- QA strategy: [QA-Automation-Strategy.md](QA-Automation-Strategy.md)
|
|
138
155
|
- Contributor workflow: [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
139
156
|
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
padding: var(--vd-draggable-padding);
|
|
61
61
|
margin: var(--vd-draggable-margin);
|
|
62
62
|
background-color: var(--vd-draggable-bg);
|
|
63
|
+
background-clip: padding-box;
|
|
63
64
|
border: 1px solid var(--vd-draggable-border-color);
|
|
64
65
|
border-radius: var(--vd-btn-border-radius);
|
|
65
66
|
cursor: grab;
|
|
@@ -124,6 +125,7 @@
|
|
|
124
125
|
padding: var(--vd-drop-zone-padding);
|
|
125
126
|
margin: var(--vd-draggable-margin);
|
|
126
127
|
background-color: var(--vd-drop-zone-bg);
|
|
128
|
+
background-clip: padding-box;
|
|
127
129
|
border: 2px dashed var(--vd-drop-zone-border-color);
|
|
128
130
|
border-radius: var(--vd-btn-border-radius);
|
|
129
131
|
transition: var(--vd-draggable-transition);
|
|
@@ -169,6 +171,7 @@
|
|
|
169
171
|
gap: var(--vd-draggable-margin);
|
|
170
172
|
padding: var(--vd-draggable-padding);
|
|
171
173
|
background-color: var(--vd-bg-secondary);
|
|
174
|
+
background-clip: padding-box;
|
|
172
175
|
border: 1px solid var(--vd-border-color);
|
|
173
176
|
border-radius: var(--vd-btn-border-radius);
|
|
174
177
|
}
|
|
@@ -184,6 +187,7 @@
|
|
|
184
187
|
min-width: 8rem; /* 128px */
|
|
185
188
|
padding: var(--vd-draggable-padding);
|
|
186
189
|
background-color: var(--vd-draggable-bg);
|
|
190
|
+
background-clip: padding-box;
|
|
187
191
|
border: 1px solid var(--vd-draggable-border-color);
|
|
188
192
|
border-radius: var(--vd-btn-border-radius);
|
|
189
193
|
cursor: grab;
|
package/css/components/forms.css
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanduo Framework - Theme Switcher
|
|
3
|
+
* Icon-only menu variant for explicit theme selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--vd-theme-switcher-menu-bg: var(--vd-color-white);
|
|
8
|
+
--vd-theme-switcher-menu-border-color: var(--vd-border-color);
|
|
9
|
+
--vd-theme-switcher-option-hover-bg: var(--vd-bg-secondary);
|
|
10
|
+
--vd-theme-switcher-option-active-bg: var(--vd-color-primary-alpha-10);
|
|
11
|
+
--vd-theme-switcher-option-active-color: var(--vd-color-primary);
|
|
12
|
+
--vd-theme-switcher-z-index: 1000;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
[data-theme="dark"] {
|
|
16
|
+
--vd-theme-switcher-menu-bg: var(--vd-bg-primary);
|
|
17
|
+
--vd-theme-switcher-option-hover-bg: var(--vd-bg-secondary);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root:not([data-theme]) {
|
|
22
|
+
--vd-theme-switcher-menu-bg: var(--vd-bg-primary);
|
|
23
|
+
--vd-theme-switcher-option-hover-bg: var(--vd-bg-secondary);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.vd-theme-switcher {
|
|
28
|
+
position: relative;
|
|
29
|
+
display: inline-block;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vd-theme-switcher-toggle {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
width: 2.375rem;
|
|
37
|
+
height: 2.375rem;
|
|
38
|
+
padding: 0;
|
|
39
|
+
border: 1px solid var(--vd-border-color);
|
|
40
|
+
border-radius: var(--vd-btn-border-radius, var(--vd-card-border-radius));
|
|
41
|
+
background: transparent;
|
|
42
|
+
color: var(--vd-text-secondary);
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
font-size: 1.15rem;
|
|
45
|
+
line-height: 1;
|
|
46
|
+
transition: color 0.2s, border-color 0.2s, background 0.2s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.vd-theme-switcher-toggle:hover,
|
|
50
|
+
.vd-theme-switcher-toggle:focus-visible {
|
|
51
|
+
color: var(--vd-color-primary);
|
|
52
|
+
border-color: var(--vd-color-primary);
|
|
53
|
+
background: var(--vd-color-primary-alpha-10);
|
|
54
|
+
outline: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.vd-theme-switcher-menu {
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: calc(100% + 0.375rem);
|
|
60
|
+
left: 0;
|
|
61
|
+
z-index: var(--vd-theme-switcher-z-index);
|
|
62
|
+
display: none;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 0.25rem;
|
|
65
|
+
min-width: auto;
|
|
66
|
+
padding: 0.375rem;
|
|
67
|
+
margin: 0;
|
|
68
|
+
background: var(--vd-theme-switcher-menu-bg, #ffffff);
|
|
69
|
+
border: 1px solid var(--vd-theme-switcher-menu-border-color);
|
|
70
|
+
border-radius: var(--vd-btn-border-radius, var(--vd-card-border-radius));
|
|
71
|
+
box-shadow: var(--vd-shadow-lg);
|
|
72
|
+
list-style: none;
|
|
73
|
+
isolation: isolate;
|
|
74
|
+
backdrop-filter: none;
|
|
75
|
+
-webkit-backdrop-filter: none;
|
|
76
|
+
visibility: hidden;
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
transform: translateY(-6px);
|
|
79
|
+
transition: visibility var(--vd-transition-duration-base, 0.2s) var(--vd-transition-ease, ease),
|
|
80
|
+
transform var(--vd-transition-duration-base, 0.2s) var(--vd-transition-ease, ease);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.vd-theme-switcher-menu-end .vd-theme-switcher-menu,
|
|
84
|
+
.vd-theme-switcher.vd-theme-switcher-menu-end .vd-theme-switcher-menu {
|
|
85
|
+
right: 0;
|
|
86
|
+
left: auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.vd-theme-switcher.is-open .vd-theme-switcher-menu,
|
|
90
|
+
.vd-theme-switcher-menu.is-open {
|
|
91
|
+
display: flex;
|
|
92
|
+
visibility: visible;
|
|
93
|
+
pointer-events: auto;
|
|
94
|
+
transform: translateY(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.vd-theme-switcher-option,
|
|
98
|
+
.vd-theme-switcher-menu [data-theme-value] {
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
width: 2.375rem;
|
|
103
|
+
height: 2.375rem;
|
|
104
|
+
padding: 0;
|
|
105
|
+
border: 1px solid transparent;
|
|
106
|
+
border-radius: var(--vd-btn-border-radius-sm, var(--vd-radius-fib-3));
|
|
107
|
+
background: transparent;
|
|
108
|
+
color: var(--vd-text-secondary);
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
font-size: 1.15rem;
|
|
111
|
+
line-height: 1;
|
|
112
|
+
transition: color 0.2s, background 0.2s, border-color 0.2s;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.vd-theme-switcher-option:hover,
|
|
116
|
+
.vd-theme-switcher-option:focus-visible,
|
|
117
|
+
.vd-theme-switcher-menu [data-theme-value]:hover,
|
|
118
|
+
.vd-theme-switcher-menu [data-theme-value]:focus-visible {
|
|
119
|
+
color: var(--vd-text-primary);
|
|
120
|
+
background: var(--vd-theme-switcher-option-hover-bg);
|
|
121
|
+
outline: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.vd-theme-switcher-option.is-active,
|
|
125
|
+
.vd-theme-switcher-menu [data-theme-value].is-active {
|
|
126
|
+
color: var(--vd-theme-switcher-option-active-color);
|
|
127
|
+
background: var(--vd-theme-switcher-option-active-bg);
|
|
128
|
+
border-color: var(--vd-color-primary-alpha-20, transparent);
|
|
129
|
+
}
|
package/css/vanduo.css
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
@import url('components/spinner.css');
|
|
54
54
|
@import url('components/button-group.css');
|
|
55
55
|
@import url('components/theme-customizer.css');
|
|
56
|
+
@import url('components/theme-switcher.css');
|
|
56
57
|
@import url('components/image-box.css');
|
|
57
58
|
@import url('components/doc-tabs.css');
|
|
58
59
|
@import url('components/doc-search.css');
|
package/dist/build-info.json
CHANGED
package/dist/vanduo.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Vanduo v1.4.
|
|
1
|
+
/*! Vanduo v1.4.5 | Built: 2026-06-11T18:11:31.153Z | git:70ed923 | development */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -201,7 +201,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
201
201
|
// js/vanduo.js
|
|
202
202
|
(function() {
|
|
203
203
|
"use strict";
|
|
204
|
-
const VANDUO_VERSION = true ? "1.4.
|
|
204
|
+
const VANDUO_VERSION = true ? "1.4.5" : "0.0.0-dev";
|
|
205
205
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
206
206
|
const Vanduo2 = {
|
|
207
207
|
version: VANDUO_VERSION,
|
|
@@ -486,9 +486,10 @@ module.exports = __toCommonJS(index_exports);
|
|
|
486
486
|
init: function(root) {
|
|
487
487
|
const snippets = this.queryWithin(root, ".vd-code-snippet");
|
|
488
488
|
snippets.forEach((snippet) => {
|
|
489
|
-
if (
|
|
490
|
-
|
|
489
|
+
if (snippet.dataset.initialized === "true") {
|
|
490
|
+
return;
|
|
491
491
|
}
|
|
492
|
+
this.initSnippet(snippet);
|
|
492
493
|
});
|
|
493
494
|
},
|
|
494
495
|
/**
|
|
@@ -496,7 +497,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
496
497
|
* @param {HTMLElement} snippet - Code snippet container element
|
|
497
498
|
*/
|
|
498
499
|
initSnippet: function(snippet) {
|
|
499
|
-
snippet.dataset.initialized
|
|
500
|
+
if (snippet.dataset.initialized === "true") {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
500
503
|
snippet._codeSnippetCleanup = [];
|
|
501
504
|
const toggle = snippet.querySelector(".vd-code-snippet-toggle");
|
|
502
505
|
const content = snippet.querySelector(".vd-code-snippet-content");
|
|
@@ -524,6 +527,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
524
527
|
lineNumberPanes.forEach((pane) => {
|
|
525
528
|
this.addLineNumbers(pane);
|
|
526
529
|
});
|
|
530
|
+
snippet.dataset.initialized = "true";
|
|
527
531
|
},
|
|
528
532
|
/**
|
|
529
533
|
* Initialize collapsible functionality
|
|
@@ -533,13 +537,14 @@ module.exports = __toCommonJS(index_exports);
|
|
|
533
537
|
*/
|
|
534
538
|
initCollapsible: function(snippet, toggle, content) {
|
|
535
539
|
const isExpanded = snippet.dataset.expanded === "true";
|
|
536
|
-
toggle.setAttribute("aria-expanded", isExpanded);
|
|
537
|
-
content.dataset.visible = isExpanded;
|
|
540
|
+
toggle.setAttribute("aria-expanded", isExpanded ? "true" : "false");
|
|
541
|
+
content.dataset.visible = isExpanded ? "true" : "false";
|
|
538
542
|
this.addListener(snippet, toggle, "click", () => {
|
|
539
543
|
const expanded = snippet.dataset.expanded === "true";
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
544
|
+
const nextExpanded = !expanded;
|
|
545
|
+
snippet.dataset.expanded = nextExpanded ? "true" : "false";
|
|
546
|
+
toggle.setAttribute("aria-expanded", nextExpanded ? "true" : "false");
|
|
547
|
+
content.dataset.visible = nextExpanded ? "true" : "false";
|
|
543
548
|
if (!expanded) {
|
|
544
549
|
const extractPanes = content.querySelectorAll("[data-extract]:not([data-extracted])");
|
|
545
550
|
extractPanes.forEach((pane) => {
|
|
@@ -5070,21 +5075,45 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5070
5075
|
// js/components/theme-switcher.js
|
|
5071
5076
|
(function() {
|
|
5072
5077
|
"use strict";
|
|
5078
|
+
const THEME_MODES = ["system", "light", "dark"];
|
|
5079
|
+
const THEME_ICON_CLASSES = {
|
|
5080
|
+
system: "ph ph-desktop",
|
|
5081
|
+
light: "ph ph-sun",
|
|
5082
|
+
dark: "ph ph-moon"
|
|
5083
|
+
};
|
|
5084
|
+
const THEME_LABELS = {
|
|
5085
|
+
system: "Theme: System",
|
|
5086
|
+
light: "Theme: Light",
|
|
5087
|
+
dark: "Theme: Dark"
|
|
5088
|
+
};
|
|
5089
|
+
const THEME_OPTION_TOOLTIPS = {
|
|
5090
|
+
system: "Use system preference",
|
|
5091
|
+
light: "Light theme",
|
|
5092
|
+
dark: "Dark theme"
|
|
5093
|
+
};
|
|
5073
5094
|
const ThemeSwitcher = {
|
|
5074
5095
|
isInitialized: false,
|
|
5075
5096
|
_mediaQuery: null,
|
|
5076
5097
|
_onMediaChange: null,
|
|
5098
|
+
menuInstances: /* @__PURE__ */ new Map(),
|
|
5077
5099
|
getToggles: function(root) {
|
|
5100
|
+
const scope = root || document;
|
|
5101
|
+
const toggles = window.Vanduo && typeof window.Vanduo.queryAll === "function" ? window.Vanduo.queryAll(scope, '[data-toggle="theme"]') : Array.from(scope.querySelectorAll('[data-toggle="theme"]'));
|
|
5102
|
+
return toggles.filter(function(toggle) {
|
|
5103
|
+
return !toggle.closest('.vd-theme-switcher[data-theme-ui="menu"]');
|
|
5104
|
+
});
|
|
5105
|
+
},
|
|
5106
|
+
getMenuSwitchers: function(root) {
|
|
5107
|
+
const scope = root || document;
|
|
5078
5108
|
if (window.Vanduo && typeof window.Vanduo.queryAll === "function") {
|
|
5079
|
-
return window.Vanduo.queryAll(
|
|
5109
|
+
return window.Vanduo.queryAll(scope, '.vd-theme-switcher[data-theme-ui="menu"]');
|
|
5080
5110
|
}
|
|
5081
|
-
return Array.from(
|
|
5111
|
+
return Array.from(scope.querySelectorAll('.vd-theme-switcher[data-theme-ui="menu"]'));
|
|
5082
5112
|
},
|
|
5083
5113
|
init: function(root) {
|
|
5084
5114
|
this.STORAGE_KEY = "vanduo-theme-preference";
|
|
5085
5115
|
this.state = {
|
|
5086
5116
|
preference: this.getPreference()
|
|
5087
|
-
// 'light', 'dark', or 'system'
|
|
5088
5117
|
};
|
|
5089
5118
|
if (this.isInitialized) {
|
|
5090
5119
|
this.applyTheme();
|
|
@@ -5101,6 +5130,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5101
5130
|
return this.getStorageValue(this.STORAGE_KEY, "system");
|
|
5102
5131
|
},
|
|
5103
5132
|
setPreference: function(pref) {
|
|
5133
|
+
if (!THEME_MODES.includes(pref)) {
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5104
5136
|
this.state.preference = pref;
|
|
5105
5137
|
this.setStorageValue(this.STORAGE_KEY, pref);
|
|
5106
5138
|
this.applyTheme();
|
|
@@ -5154,8 +5186,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5154
5186
|
};
|
|
5155
5187
|
this._mediaQuery.addEventListener("change", this._onMediaChange);
|
|
5156
5188
|
},
|
|
5157
|
-
// Helper to facilitate UI creation if needed, though often UI is in HTML
|
|
5158
5189
|
renderUI: function(root) {
|
|
5190
|
+
this.renderMenuSwitchers(root);
|
|
5159
5191
|
const toggles = this.getToggles(root);
|
|
5160
5192
|
toggles.forEach((toggle) => {
|
|
5161
5193
|
if (toggle.getAttribute("data-theme-initialized") === "true") {
|
|
@@ -5173,7 +5205,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5173
5205
|
toggle._themeToggleHandler = onChange;
|
|
5174
5206
|
} else {
|
|
5175
5207
|
const onClick = () => {
|
|
5176
|
-
const modes =
|
|
5208
|
+
const modes = THEME_MODES;
|
|
5177
5209
|
const nextIndex = (modes.indexOf(this.state.preference) + 1) % modes.length;
|
|
5178
5210
|
this.setPreference(modes[nextIndex]);
|
|
5179
5211
|
};
|
|
@@ -5182,6 +5214,188 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5182
5214
|
}
|
|
5183
5215
|
toggle.setAttribute("data-theme-initialized", "true");
|
|
5184
5216
|
});
|
|
5217
|
+
this.updateUI(root);
|
|
5218
|
+
},
|
|
5219
|
+
renderMenuSwitchers: function(root) {
|
|
5220
|
+
const switchers = this.getMenuSwitchers(root);
|
|
5221
|
+
switchers.forEach((switcher) => {
|
|
5222
|
+
if (switcher.getAttribute("data-theme-menu-initialized") === "true") {
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5225
|
+
const toggle = switcher.querySelector(".vd-theme-switcher-toggle");
|
|
5226
|
+
const menu = switcher.querySelector(".vd-theme-switcher-menu");
|
|
5227
|
+
if (!toggle || !menu) {
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5230
|
+
const options = menu.querySelectorAll("[data-theme-value]");
|
|
5231
|
+
const cleanupFunctions = [];
|
|
5232
|
+
toggle.setAttribute("aria-haspopup", "true");
|
|
5233
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
5234
|
+
menu.setAttribute("aria-hidden", "true");
|
|
5235
|
+
const toggleClickHandler = (e) => {
|
|
5236
|
+
e.preventDefault();
|
|
5237
|
+
e.stopPropagation();
|
|
5238
|
+
this.toggleMenu(switcher, toggle, menu);
|
|
5239
|
+
};
|
|
5240
|
+
toggle.addEventListener("click", toggleClickHandler);
|
|
5241
|
+
cleanupFunctions.push(() => toggle.removeEventListener("click", toggleClickHandler));
|
|
5242
|
+
options.forEach((option) => {
|
|
5243
|
+
const optionClickHandler = (e) => {
|
|
5244
|
+
e.preventDefault();
|
|
5245
|
+
e.stopPropagation();
|
|
5246
|
+
const value = option.getAttribute("data-theme-value");
|
|
5247
|
+
if (value) {
|
|
5248
|
+
this.setPreference(value);
|
|
5249
|
+
}
|
|
5250
|
+
this.closeMenu(switcher, toggle, menu);
|
|
5251
|
+
};
|
|
5252
|
+
option.addEventListener("click", optionClickHandler);
|
|
5253
|
+
cleanupFunctions.push(() => option.removeEventListener("click", optionClickHandler));
|
|
5254
|
+
const optionKeydownHandler = (e) => {
|
|
5255
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
5256
|
+
e.preventDefault();
|
|
5257
|
+
optionClickHandler(e);
|
|
5258
|
+
}
|
|
5259
|
+
};
|
|
5260
|
+
option.addEventListener("keydown", optionKeydownHandler);
|
|
5261
|
+
cleanupFunctions.push(() => option.removeEventListener("keydown", optionKeydownHandler));
|
|
5262
|
+
});
|
|
5263
|
+
const toggleKeydownHandler = (e) => {
|
|
5264
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
5265
|
+
e.preventDefault();
|
|
5266
|
+
if (!menu.classList.contains("is-open")) {
|
|
5267
|
+
this.openMenu(switcher, toggle, menu);
|
|
5268
|
+
}
|
|
5269
|
+
} else if (e.key === "Escape" && menu.classList.contains("is-open")) {
|
|
5270
|
+
e.preventDefault();
|
|
5271
|
+
this.closeMenu(switcher, toggle, menu);
|
|
5272
|
+
}
|
|
5273
|
+
};
|
|
5274
|
+
toggle.addEventListener("keydown", toggleKeydownHandler);
|
|
5275
|
+
cleanupFunctions.push(() => toggle.removeEventListener("keydown", toggleKeydownHandler));
|
|
5276
|
+
const menuKeydownHandler = (e) => {
|
|
5277
|
+
this.handleMenuKeydown(e, switcher, toggle, menu, options);
|
|
5278
|
+
};
|
|
5279
|
+
menu.addEventListener("keydown", menuKeydownHandler);
|
|
5280
|
+
cleanupFunctions.push(() => menu.removeEventListener("keydown", menuKeydownHandler));
|
|
5281
|
+
const documentClickHandler = (e) => {
|
|
5282
|
+
if (!switcher.contains(e.target) && menu.classList.contains("is-open")) {
|
|
5283
|
+
this.closeMenu(switcher, toggle, menu);
|
|
5284
|
+
}
|
|
5285
|
+
};
|
|
5286
|
+
document.addEventListener("click", documentClickHandler);
|
|
5287
|
+
cleanupFunctions.push(() => document.removeEventListener("click", documentClickHandler));
|
|
5288
|
+
this.menuInstances.set(switcher, { toggle, menu, cleanup: cleanupFunctions });
|
|
5289
|
+
switcher.setAttribute("data-theme-menu-initialized", "true");
|
|
5290
|
+
this.initMenuTooltips(switcher);
|
|
5291
|
+
});
|
|
5292
|
+
},
|
|
5293
|
+
initMenuTooltips: function(switcher) {
|
|
5294
|
+
const tooltips = window.Vanduo && typeof window.Vanduo.getComponent === "function" ? window.Vanduo.getComponent("tooltips") : null;
|
|
5295
|
+
if (tooltips && typeof tooltips.init === "function") {
|
|
5296
|
+
tooltips.init(switcher);
|
|
5297
|
+
}
|
|
5298
|
+
},
|
|
5299
|
+
closeOtherMenus: function(exceptMenu) {
|
|
5300
|
+
this.menuInstances.forEach((instance, switcher) => {
|
|
5301
|
+
if (instance.menu !== exceptMenu && instance.menu.classList.contains("is-open")) {
|
|
5302
|
+
this.closeMenu(switcher, instance.toggle, instance.menu);
|
|
5303
|
+
}
|
|
5304
|
+
});
|
|
5305
|
+
},
|
|
5306
|
+
toggleMenu: function(switcher, toggle, menu) {
|
|
5307
|
+
if (menu.classList.contains("is-open")) {
|
|
5308
|
+
this.closeMenu(switcher, toggle, menu);
|
|
5309
|
+
} else {
|
|
5310
|
+
this.openMenu(switcher, toggle, menu);
|
|
5311
|
+
}
|
|
5312
|
+
},
|
|
5313
|
+
openMenu: function(switcher, toggle, menu) {
|
|
5314
|
+
this.closeOtherMenus(menu);
|
|
5315
|
+
switcher.classList.add("is-open");
|
|
5316
|
+
menu.classList.add("is-open");
|
|
5317
|
+
toggle.setAttribute("aria-expanded", "true");
|
|
5318
|
+
menu.setAttribute("aria-hidden", "false");
|
|
5319
|
+
const activeOption = menu.querySelector("[data-theme-value].is-active") || menu.querySelector("[data-theme-value]");
|
|
5320
|
+
if (activeOption) {
|
|
5321
|
+
setTimeout(() => activeOption.focus(), 0);
|
|
5322
|
+
}
|
|
5323
|
+
},
|
|
5324
|
+
closeMenu: function(switcher, toggle, menu) {
|
|
5325
|
+
switcher.classList.remove("is-open");
|
|
5326
|
+
menu.classList.remove("is-open");
|
|
5327
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
5328
|
+
menu.setAttribute("aria-hidden", "true");
|
|
5329
|
+
},
|
|
5330
|
+
handleMenuKeydown: function(e, switcher, toggle, menu, options) {
|
|
5331
|
+
const items = Array.from(options);
|
|
5332
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
5333
|
+
if (e.key === "Escape") {
|
|
5334
|
+
e.preventDefault();
|
|
5335
|
+
this.closeMenu(switcher, toggle, menu);
|
|
5336
|
+
toggle.focus();
|
|
5337
|
+
return;
|
|
5338
|
+
}
|
|
5339
|
+
if (e.key === "ArrowDown") {
|
|
5340
|
+
e.preventDefault();
|
|
5341
|
+
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
5342
|
+
items[nextIndex].focus();
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5345
|
+
if (e.key === "ArrowUp") {
|
|
5346
|
+
e.preventDefault();
|
|
5347
|
+
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
5348
|
+
items[prevIndex].focus();
|
|
5349
|
+
}
|
|
5350
|
+
},
|
|
5351
|
+
updateMenuSwitcher: function(switcher) {
|
|
5352
|
+
const toggle = switcher.querySelector(".vd-theme-switcher-toggle");
|
|
5353
|
+
const menu = switcher.querySelector(".vd-theme-switcher-menu");
|
|
5354
|
+
if (!toggle || !menu) {
|
|
5355
|
+
return;
|
|
5356
|
+
}
|
|
5357
|
+
const pref = this.state.preference;
|
|
5358
|
+
const icon = toggle.querySelector("[data-theme-icon]");
|
|
5359
|
+
const label = THEME_LABELS[pref] || THEME_LABELS.system;
|
|
5360
|
+
if (icon) {
|
|
5361
|
+
icon.className = THEME_ICON_CLASSES[pref] || THEME_ICON_CLASSES.system;
|
|
5362
|
+
}
|
|
5363
|
+
toggle.setAttribute("aria-label", label);
|
|
5364
|
+
if (toggle.hasAttribute("data-tooltip")) {
|
|
5365
|
+
toggle.setAttribute("data-tooltip", label);
|
|
5366
|
+
this.refreshTooltipContent(toggle, label);
|
|
5367
|
+
}
|
|
5368
|
+
menu.querySelectorAll("[data-theme-value]").forEach((option) => {
|
|
5369
|
+
const value = option.getAttribute("data-theme-value");
|
|
5370
|
+
const isActive = value === pref;
|
|
5371
|
+
option.classList.toggle("is-active", isActive);
|
|
5372
|
+
option.setAttribute("aria-checked", isActive ? "true" : "false");
|
|
5373
|
+
const tooltipText = THEME_OPTION_TOOLTIPS[value];
|
|
5374
|
+
if (tooltipText && option.hasAttribute("data-tooltip")) {
|
|
5375
|
+
option.setAttribute("data-tooltip", tooltipText);
|
|
5376
|
+
this.refreshTooltipContent(option, tooltipText);
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5379
|
+
},
|
|
5380
|
+
refreshTooltipContent: function(element, text) {
|
|
5381
|
+
const tooltips = window.Vanduo && typeof window.Vanduo.getComponent === "function" ? window.Vanduo.getComponent("tooltips") : null;
|
|
5382
|
+
if (!tooltips || typeof tooltips.update !== "function") {
|
|
5383
|
+
return;
|
|
5384
|
+
}
|
|
5385
|
+
tooltips.update(element, text);
|
|
5386
|
+
},
|
|
5387
|
+
updateCycleToggle: function(toggle) {
|
|
5388
|
+
const pref = this.state.preference;
|
|
5389
|
+
const icon = toggle.querySelector("[data-theme-icon]");
|
|
5390
|
+
const label = THEME_LABELS[pref] || THEME_LABELS.system;
|
|
5391
|
+
if (icon) {
|
|
5392
|
+
icon.className = THEME_ICON_CLASSES[pref] || THEME_ICON_CLASSES.system;
|
|
5393
|
+
}
|
|
5394
|
+
toggle.setAttribute("aria-label", label);
|
|
5395
|
+
if (toggle.hasAttribute("data-tooltip")) {
|
|
5396
|
+
toggle.setAttribute("data-tooltip", label);
|
|
5397
|
+
this.refreshTooltipContent(toggle, label);
|
|
5398
|
+
}
|
|
5185
5399
|
},
|
|
5186
5400
|
updateUI: function(root) {
|
|
5187
5401
|
const toggles = this.getToggles(root);
|
|
@@ -5193,11 +5407,26 @@ module.exports = __toCommonJS(index_exports);
|
|
|
5193
5407
|
if (span) {
|
|
5194
5408
|
span.textContent = this.state.preference.charAt(0).toUpperCase() + this.state.preference.slice(1);
|
|
5195
5409
|
}
|
|
5410
|
+
if (toggle.querySelector("[data-theme-icon]")) {
|
|
5411
|
+
this.updateCycleToggle(toggle);
|
|
5412
|
+
}
|
|
5196
5413
|
}
|
|
5197
5414
|
});
|
|
5415
|
+
this.getMenuSwitchers(root).forEach((switcher) => {
|
|
5416
|
+
this.updateMenuSwitcher(switcher);
|
|
5417
|
+
});
|
|
5198
5418
|
},
|
|
5199
5419
|
destroyAll: function(root) {
|
|
5200
5420
|
const scope = root || document;
|
|
5421
|
+
this.getMenuSwitchers(scope).forEach((switcher) => {
|
|
5422
|
+
const instance = this.menuInstances.get(switcher);
|
|
5423
|
+
if (instance) {
|
|
5424
|
+
instance.cleanup.forEach((fn) => fn());
|
|
5425
|
+
this.closeMenu(switcher, instance.toggle, instance.menu);
|
|
5426
|
+
this.menuInstances.delete(switcher);
|
|
5427
|
+
}
|
|
5428
|
+
switcher.removeAttribute("data-theme-menu-initialized");
|
|
5429
|
+
});
|
|
5201
5430
|
const toggles = this.getToggles(scope).filter(function(toggle) {
|
|
5202
5431
|
return toggle.getAttribute("data-theme-initialized") === "true";
|
|
5203
5432
|
});
|