vanduo-framework 1.1.8
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/LICENSE +35 -0
- package/README.md +205 -0
- package/css/components/alerts.css +224 -0
- package/css/components/avatar.css +275 -0
- package/css/components/badges.css +230 -0
- package/css/components/breadcrumbs.css +146 -0
- package/css/components/button-group.css +82 -0
- package/css/components/buttons.css +530 -0
- package/css/components/cards.css +304 -0
- package/css/components/chips.css +259 -0
- package/css/components/code-snippet.css +555 -0
- package/css/components/collapsible.css +267 -0
- package/css/components/collections.css +253 -0
- package/css/components/doc-search.css +464 -0
- package/css/components/doc-tabs.css +38 -0
- package/css/components/draggable.css +317 -0
- package/css/components/dropdown.css +266 -0
- package/css/components/footer.css +375 -0
- package/css/components/forms.css +1774 -0
- package/css/components/image-box.css +279 -0
- package/css/components/modals.css +285 -0
- package/css/components/navbar.css +530 -0
- package/css/components/pagination.css +186 -0
- package/css/components/preloader.css +340 -0
- package/css/components/progress.css +107 -0
- package/css/components/sidenav.css +301 -0
- package/css/components/skeleton.css +241 -0
- package/css/components/spinner.css +144 -0
- package/css/components/tabs.css +327 -0
- package/css/components/theme-customizer.css +835 -0
- package/css/components/toast.css +357 -0
- package/css/components/tooltips.css +270 -0
- package/css/core/colors.css +1017 -0
- package/css/core/fonts.css +266 -0
- package/css/core/grid.css +1699 -0
- package/css/core/helpers.css +2202 -0
- package/css/core/reset.css +128 -0
- package/css/core/tokens.css +213 -0
- package/css/core/typography.css +405 -0
- package/css/core/vd-aliases.css +47 -0
- package/css/effects/parallax.css +113 -0
- package/css/icons/icons-all.css +23 -0
- package/css/icons/icons.css +25 -0
- package/css/utilities/media.css +167 -0
- package/css/utilities/print.css +111 -0
- package/css/utilities/shadow.css +243 -0
- package/css/utilities/table.css +381 -0
- package/css/utilities/transforms.css +71 -0
- package/css/utilities/transitions.css +87 -0
- package/css/vanduo.css +80 -0
- package/dist/build-info.json +6 -0
- package/dist/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
- package/dist/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
- package/dist/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
- package/dist/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
- package/dist/fonts/inter/inter-bold.woff2 +0 -0
- package/dist/fonts/inter/inter-medium.woff2 +0 -0
- package/dist/fonts/inter/inter-regular.woff2 +0 -0
- package/dist/fonts/inter/inter-semibold.woff2 +0 -0
- package/dist/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
- package/dist/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-bold.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-medium.woff2 +0 -0
- package/dist/fonts/open-sans/open-sans-regular.woff2 +0 -0
- package/dist/fonts/rubik/rubik-bold.woff2 +0 -0
- package/dist/fonts/rubik/rubik-medium.woff2 +0 -0
- package/dist/fonts/rubik/rubik-regular.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-bold.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-regular.woff2 +0 -0
- package/dist/fonts/source-sans/source-sans-semibold.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
- package/dist/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
- package/dist/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
- package/dist/icons/phosphor/LICENSE +21 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
- package/dist/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
- package/dist/icons/phosphor/bold/style.css +4627 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
- package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
- package/dist/icons/phosphor/duotone/style.css +12115 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
- package/dist/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
- package/dist/icons/phosphor/fill/style.css +4627 -0
- package/dist/icons/phosphor/light/Phosphor-Light.ttf +0 -0
- package/dist/icons/phosphor/light/Phosphor-Light.woff +0 -0
- package/dist/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
- package/dist/icons/phosphor/light/style.css +4627 -0
- package/dist/icons/phosphor/regular/Phosphor.ttf +0 -0
- package/dist/icons/phosphor/regular/Phosphor.woff +0 -0
- package/dist/icons/phosphor/regular/Phosphor.woff2 +0 -0
- package/dist/icons/phosphor/regular/style.css +4627 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
- package/dist/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
- package/dist/icons/phosphor/thin/style.css +4627 -0
- package/dist/vanduo.cjs.js +5569 -0
- package/dist/vanduo.cjs.js.map +7 -0
- package/dist/vanduo.cjs.min.js +48 -0
- package/dist/vanduo.cjs.min.js.map +7 -0
- package/dist/vanduo.css +60666 -0
- package/dist/vanduo.css.map +1 -0
- package/dist/vanduo.esm.js +5548 -0
- package/dist/vanduo.esm.js.map +7 -0
- package/dist/vanduo.esm.min.js +48 -0
- package/dist/vanduo.esm.min.js.map +7 -0
- package/dist/vanduo.js +5545 -0
- package/dist/vanduo.js.map +7 -0
- package/dist/vanduo.min.css +2 -0
- package/dist/vanduo.min.css.map +1 -0
- package/dist/vanduo.min.js +48 -0
- package/dist/vanduo.min.js.map +7 -0
- package/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
- package/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
- package/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
- package/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
- package/fonts/inter/inter-bold.woff2 +0 -0
- package/fonts/inter/inter-medium.woff2 +0 -0
- package/fonts/inter/inter-regular.woff2 +0 -0
- package/fonts/inter/inter-semibold.woff2 +0 -0
- package/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
- package/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
- package/fonts/open-sans/open-sans-bold.woff2 +0 -0
- package/fonts/open-sans/open-sans-medium.woff2 +0 -0
- package/fonts/open-sans/open-sans-regular.woff2 +0 -0
- package/fonts/rubik/rubik-bold.woff2 +0 -0
- package/fonts/rubik/rubik-medium.woff2 +0 -0
- package/fonts/rubik/rubik-regular.woff2 +0 -0
- package/fonts/source-sans/source-sans-bold.woff2 +0 -0
- package/fonts/source-sans/source-sans-regular.woff2 +0 -0
- package/fonts/source-sans/source-sans-semibold.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
- package/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
- package/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
- package/icons/phosphor/LICENSE +21 -0
- package/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
- package/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
- package/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
- package/icons/phosphor/bold/style.css +4627 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
- package/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
- package/icons/phosphor/duotone/style.css +12115 -0
- package/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
- package/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
- package/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
- package/icons/phosphor/fill/style.css +4627 -0
- package/icons/phosphor/light/Phosphor-Light.ttf +0 -0
- package/icons/phosphor/light/Phosphor-Light.woff +0 -0
- package/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
- package/icons/phosphor/light/style.css +4627 -0
- package/icons/phosphor/regular/Phosphor.ttf +0 -0
- package/icons/phosphor/regular/Phosphor.woff +0 -0
- package/icons/phosphor/regular/Phosphor.woff2 +0 -0
- package/icons/phosphor/regular/style.css +4627 -0
- package/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
- package/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
- package/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
- package/icons/phosphor/thin/style.css +4627 -0
- package/js/components/code-snippet.js +639 -0
- package/js/components/collapsible.js +226 -0
- package/js/components/doc-search.js +936 -0
- package/js/components/draggable.js +725 -0
- package/js/components/dropdown.js +362 -0
- package/js/components/font-switcher.js +253 -0
- package/js/components/grid.js +279 -0
- package/js/components/image-box.js +372 -0
- package/js/components/modals.js +367 -0
- package/js/components/navbar.js +264 -0
- package/js/components/pagination.js +286 -0
- package/js/components/parallax.js +216 -0
- package/js/components/preloader.js +183 -0
- package/js/components/select.js +444 -0
- package/js/components/sidenav.js +303 -0
- package/js/components/tabs.js +303 -0
- package/js/components/theme-customizer.js +784 -0
- package/js/components/theme-switcher.js +183 -0
- package/js/components/toast.js +343 -0
- package/js/components/tooltips.js +306 -0
- package/js/index.js +52 -0
- package/js/utils/helpers.js +306 -0
- package/js/utils/lifecycle.js +135 -0
- package/js/vanduo.js +120 -0
- package/package.json +78 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanduo Framework - Theme Customizer
|
|
3
|
+
* A comprehensive theme customization component for the navbar
|
|
4
|
+
* Handles primary color, neutral color, radius, font, and color mode
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const ThemeCustomizer = {
|
|
11
|
+
// Storage keys
|
|
12
|
+
STORAGE_KEYS: {
|
|
13
|
+
PRIMARY: 'vanduo-primary-color',
|
|
14
|
+
NEUTRAL: 'vanduo-neutral-color',
|
|
15
|
+
RADIUS: 'vanduo-radius',
|
|
16
|
+
FONT: 'vanduo-font-preference',
|
|
17
|
+
THEME: 'vanduo-theme-preference'
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// Default values
|
|
21
|
+
DEFAULTS: {
|
|
22
|
+
PRIMARY_LIGHT: 'black',
|
|
23
|
+
PRIMARY_DARK: 'amber',
|
|
24
|
+
NEUTRAL: 'neutral',
|
|
25
|
+
RADIUS: '0.5',
|
|
26
|
+
FONT: 'ubuntu',
|
|
27
|
+
THEME: 'system'
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Primary color definitions (Open Color based)
|
|
31
|
+
PRIMARY_COLORS: {
|
|
32
|
+
'black': { name: 'Black', color: '#000000' },
|
|
33
|
+
'red': { name: 'Red', color: '#fa5252' },
|
|
34
|
+
'orange': { name: 'Orange', color: '#fd7e14' },
|
|
35
|
+
'amber': { name: 'Amber', color: '#f59f00' },
|
|
36
|
+
'yellow': { name: 'Yellow', color: '#fcc419' },
|
|
37
|
+
'lime': { name: 'Lime', color: '#82c91e' },
|
|
38
|
+
'green': { name: 'Green', color: '#40c057' },
|
|
39
|
+
'emerald': { name: 'Emerald', color: '#20c997' },
|
|
40
|
+
'teal': { name: 'Teal', color: '#12b886' },
|
|
41
|
+
'cyan': { name: 'Cyan', color: '#22b8cf' },
|
|
42
|
+
'sky': { name: 'Sky', color: '#3bc9db' },
|
|
43
|
+
'blue': { name: 'Blue', color: '#228be6' },
|
|
44
|
+
'indigo': { name: 'Indigo', color: '#4c6ef5' },
|
|
45
|
+
'violet': { name: 'Violet', color: '#7950f2' },
|
|
46
|
+
'purple': { name: 'Purple', color: '#be4bdb' },
|
|
47
|
+
'fuchsia': { name: 'Fuchsia', color: '#f06595' },
|
|
48
|
+
'pink': { name: 'Pink', color: '#e64980' },
|
|
49
|
+
'rose': { name: 'Rose', color: '#ff8787' }
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Neutral color definitions
|
|
53
|
+
NEUTRAL_COLORS: {
|
|
54
|
+
'slate': { name: 'Slate', color: '#64748b' },
|
|
55
|
+
'gray': { name: 'Gray', color: '#6b7280' },
|
|
56
|
+
'zinc': { name: 'Zinc', color: '#71717a' },
|
|
57
|
+
'neutral': { name: 'Neutral', color: '#737373' },
|
|
58
|
+
'stone': { name: 'Stone', color: '#78716c' }
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Radius options
|
|
62
|
+
RADIUS_OPTIONS: ['0', '0.125', '0.25', '0.375', '0.5'],
|
|
63
|
+
|
|
64
|
+
// Font options
|
|
65
|
+
FONT_OPTIONS: {
|
|
66
|
+
'jetbrains-mono': { name: 'JetBrains Mono', family: "'JetBrains Mono', monospace" },
|
|
67
|
+
'inter': { name: 'Inter', family: "'Inter', sans-serif" },
|
|
68
|
+
'source-sans': { name: 'Source Sans 3', family: "'Source Sans 3', sans-serif" },
|
|
69
|
+
'fira-sans': { name: 'Fira Sans', family: "'Fira Sans', sans-serif" },
|
|
70
|
+
'ibm-plex': { name: 'IBM Plex Sans', family: "'IBM Plex Sans', sans-serif" },
|
|
71
|
+
'system': { name: 'System Default', family: null },
|
|
72
|
+
// Google Fonts Collection
|
|
73
|
+
'ubuntu': { name: 'Ubuntu', family: "'Ubuntu', sans-serif" },
|
|
74
|
+
'open-sans': { name: 'Open Sans', family: "'Open Sans', sans-serif" },
|
|
75
|
+
'rubik': { name: 'Rubik', family: "'Rubik', sans-serif" },
|
|
76
|
+
'titillium-web': { name: 'Titillium Web', family: "'Titillium Web', sans-serif" }
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// Theme mode options
|
|
80
|
+
THEME_MODES: ['system', 'dark', 'light'],
|
|
81
|
+
|
|
82
|
+
// State
|
|
83
|
+
state: {
|
|
84
|
+
primary: null,
|
|
85
|
+
neutral: null,
|
|
86
|
+
radius: null,
|
|
87
|
+
font: null,
|
|
88
|
+
theme: null,
|
|
89
|
+
isOpen: false
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
isInitialized: false,
|
|
93
|
+
_cleanup: [],
|
|
94
|
+
|
|
95
|
+
// DOM references
|
|
96
|
+
elements: {
|
|
97
|
+
customizer: null,
|
|
98
|
+
trigger: null,
|
|
99
|
+
panel: null,
|
|
100
|
+
overlay: null
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialize the Theme Customizer
|
|
105
|
+
*/
|
|
106
|
+
init: function () {
|
|
107
|
+
if (this.isInitialized) {
|
|
108
|
+
this.bindExistingElements();
|
|
109
|
+
this.bindPanelEvents();
|
|
110
|
+
this.updateUI();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.isInitialized = true;
|
|
115
|
+
this._cleanup = [];
|
|
116
|
+
|
|
117
|
+
this.loadPreferences();
|
|
118
|
+
this.applyAllPreferences();
|
|
119
|
+
this.bindExistingElements();
|
|
120
|
+
this.bindEvents();
|
|
121
|
+
|
|
122
|
+
console.log('Vanduo Theme Customizer initialized');
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
addListener: function (target, event, handler, options) {
|
|
126
|
+
if (!target) return;
|
|
127
|
+
target.addEventListener(event, handler, options);
|
|
128
|
+
this._cleanup.push(() => target.removeEventListener(event, handler, options));
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get default primary color based on theme
|
|
133
|
+
*/
|
|
134
|
+
getDefaultPrimary: function (theme) {
|
|
135
|
+
if (theme === 'system') {
|
|
136
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
137
|
+
return this.DEFAULTS.PRIMARY_DARK;
|
|
138
|
+
}
|
|
139
|
+
return this.DEFAULTS.PRIMARY_LIGHT;
|
|
140
|
+
}
|
|
141
|
+
return theme === 'dark' ? this.DEFAULTS.PRIMARY_DARK : this.DEFAULTS.PRIMARY_LIGHT;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Load preferences from localStorage
|
|
146
|
+
*/
|
|
147
|
+
loadPreferences: function () {
|
|
148
|
+
this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
|
|
149
|
+
this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
|
|
150
|
+
this.state.neutral = this.getStorageValue(this.STORAGE_KEYS.NEUTRAL, this.DEFAULTS.NEUTRAL);
|
|
151
|
+
this.state.radius = this.getStorageValue(this.STORAGE_KEYS.RADIUS, this.DEFAULTS.RADIUS);
|
|
152
|
+
this.state.font = this.getStorageValue(this.STORAGE_KEYS.FONT, this.DEFAULTS.FONT);
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Save a preference to localStorage
|
|
157
|
+
*/
|
|
158
|
+
savePreference: function (key, value) {
|
|
159
|
+
this.setStorageValue(key, value);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Apply all preferences
|
|
164
|
+
*/
|
|
165
|
+
applyAllPreferences: function () {
|
|
166
|
+
this.applyPrimary(this.state.primary);
|
|
167
|
+
this.applyNeutral(this.state.neutral);
|
|
168
|
+
this.applyRadius(this.state.radius);
|
|
169
|
+
this.applyFont(this.state.font);
|
|
170
|
+
this.applyTheme(this.state.theme);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Apply primary color
|
|
175
|
+
*/
|
|
176
|
+
applyPrimary: function (colorKey) {
|
|
177
|
+
if (!this.PRIMARY_COLORS[colorKey]) {
|
|
178
|
+
colorKey = this.getDefaultPrimary(this.state.theme);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.state.primary = colorKey;
|
|
182
|
+
document.documentElement.setAttribute('data-primary', colorKey);
|
|
183
|
+
this.savePreference(this.STORAGE_KEYS.PRIMARY, colorKey);
|
|
184
|
+
|
|
185
|
+
this.dispatchEvent('primary-change', { color: colorKey });
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Apply neutral color
|
|
190
|
+
*/
|
|
191
|
+
applyNeutral: function (neutralKey) {
|
|
192
|
+
if (!this.NEUTRAL_COLORS[neutralKey]) {
|
|
193
|
+
neutralKey = this.DEFAULTS.NEUTRAL;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.state.neutral = neutralKey;
|
|
197
|
+
document.documentElement.setAttribute('data-neutral', neutralKey);
|
|
198
|
+
this.savePreference(this.STORAGE_KEYS.NEUTRAL, neutralKey);
|
|
199
|
+
|
|
200
|
+
this.dispatchEvent('neutral-change', { neutral: neutralKey });
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Apply border radius
|
|
205
|
+
*/
|
|
206
|
+
applyRadius: function (radius) {
|
|
207
|
+
if (!this.RADIUS_OPTIONS.includes(radius)) {
|
|
208
|
+
radius = this.DEFAULTS.RADIUS;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.state.radius = radius;
|
|
212
|
+
document.documentElement.setAttribute('data-radius', radius);
|
|
213
|
+
document.documentElement.style.setProperty('--radius-scale', radius);
|
|
214
|
+
this.savePreference(this.STORAGE_KEYS.RADIUS, radius);
|
|
215
|
+
|
|
216
|
+
this.dispatchEvent('radius-change', { radius: radius });
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Apply font family
|
|
221
|
+
*/
|
|
222
|
+
applyFont: function (fontKey) {
|
|
223
|
+
if (!this.FONT_OPTIONS[fontKey]) {
|
|
224
|
+
fontKey = this.DEFAULTS.FONT;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.state.font = fontKey;
|
|
228
|
+
|
|
229
|
+
if (fontKey === 'system') {
|
|
230
|
+
document.documentElement.removeAttribute('data-font');
|
|
231
|
+
} else {
|
|
232
|
+
document.documentElement.setAttribute('data-font', fontKey);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.savePreference(this.STORAGE_KEYS.FONT, fontKey);
|
|
236
|
+
|
|
237
|
+
// Also update the existing FontSwitcher if available
|
|
238
|
+
if (window.FontSwitcher && window.FontSwitcher.setPreference) {
|
|
239
|
+
window.FontSwitcher.state.preference = fontKey;
|
|
240
|
+
window.FontSwitcher.applyFont();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.dispatchEvent('font-change', { font: fontKey });
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Apply theme mode
|
|
248
|
+
*/
|
|
249
|
+
applyTheme: function (mode) {
|
|
250
|
+
if (!this.THEME_MODES.includes(mode)) {
|
|
251
|
+
mode = this.DEFAULTS.THEME;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check if we should switch primary color (if using default)
|
|
255
|
+
const oldDefault = this.getDefaultPrimary(this.state.theme);
|
|
256
|
+
if (this.state.primary === oldDefault) {
|
|
257
|
+
const newDefault = this.getDefaultPrimary(mode);
|
|
258
|
+
if (newDefault !== this.state.primary) {
|
|
259
|
+
this.applyPrimary(newDefault);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.state.theme = mode;
|
|
264
|
+
|
|
265
|
+
if (mode === 'system') {
|
|
266
|
+
document.documentElement.removeAttribute('data-theme');
|
|
267
|
+
} else {
|
|
268
|
+
document.documentElement.setAttribute('data-theme', mode);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.savePreference(this.STORAGE_KEYS.THEME, mode);
|
|
272
|
+
|
|
273
|
+
// Also update the existing ThemeSwitcher if available
|
|
274
|
+
if (window.Vanduo && window.Vanduo.components.themeSwitcher) {
|
|
275
|
+
const themeSwitcher = window.Vanduo.components.themeSwitcher;
|
|
276
|
+
if (themeSwitcher.state) {
|
|
277
|
+
themeSwitcher.state.preference = mode;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.dispatchEvent('mode-change', { mode: mode });
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Dispatch custom event
|
|
286
|
+
*/
|
|
287
|
+
dispatchEvent: function (type, detail) {
|
|
288
|
+
const event = new CustomEvent('theme:' + type, {
|
|
289
|
+
bubbles: true,
|
|
290
|
+
detail: detail
|
|
291
|
+
});
|
|
292
|
+
document.dispatchEvent(event);
|
|
293
|
+
|
|
294
|
+
// Also dispatch a general change event
|
|
295
|
+
const changeEvent = new CustomEvent('theme:change', {
|
|
296
|
+
bubbles: true,
|
|
297
|
+
detail: {
|
|
298
|
+
type: type,
|
|
299
|
+
value: detail[Object.keys(detail)[0]],
|
|
300
|
+
state: { ...this.state }
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
document.dispatchEvent(changeEvent);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Bind to existing DOM elements or create them dynamically
|
|
308
|
+
*/
|
|
309
|
+
bindExistingElements: function () {
|
|
310
|
+
// First check for existing full structure
|
|
311
|
+
this.elements.customizer = document.querySelector('.vd-theme-customizer');
|
|
312
|
+
|
|
313
|
+
if (this.elements.customizer) {
|
|
314
|
+
this.elements.trigger = this.elements.customizer.querySelector('.vd-theme-customizer-trigger');
|
|
315
|
+
this.elements.panel = this.elements.customizer.querySelector('.vd-theme-customizer-panel');
|
|
316
|
+
this.elements.overlay = this.elements.customizer.querySelector('.vd-theme-customizer-overlay');
|
|
317
|
+
} else {
|
|
318
|
+
// Look for standalone trigger button with data attribute
|
|
319
|
+
const standaloneTrigger = document.querySelector('[data-theme-customizer-trigger]');
|
|
320
|
+
if (standaloneTrigger) {
|
|
321
|
+
this.createDynamicPanel(standaloneTrigger);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Update UI to reflect current state
|
|
326
|
+
this.updateUI();
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Create the panel dynamically when only a trigger button exists
|
|
331
|
+
*/
|
|
332
|
+
createDynamicPanel: function (triggerButton) {
|
|
333
|
+
// Create wrapper
|
|
334
|
+
const wrapper = document.createElement('div');
|
|
335
|
+
wrapper.className = 'vd-theme-customizer';
|
|
336
|
+
|
|
337
|
+
// Move trigger into wrapper or create reference
|
|
338
|
+
this.elements.trigger = triggerButton;
|
|
339
|
+
|
|
340
|
+
// Create overlay
|
|
341
|
+
const overlay = document.createElement('div');
|
|
342
|
+
overlay.className = 'vd-theme-customizer-overlay';
|
|
343
|
+
|
|
344
|
+
// Create panel
|
|
345
|
+
const panel = document.createElement('div');
|
|
346
|
+
panel.className = 'vd-theme-customizer-panel';
|
|
347
|
+
panel.innerHTML = this.getPanelHTML();
|
|
348
|
+
|
|
349
|
+
// Append to body
|
|
350
|
+
document.body.appendChild(overlay);
|
|
351
|
+
document.body.appendChild(panel);
|
|
352
|
+
|
|
353
|
+
// Store references
|
|
354
|
+
this.elements.panel = panel;
|
|
355
|
+
this.elements.overlay = overlay;
|
|
356
|
+
this.elements.customizer = { contains: (el) => panel.contains(el) || triggerButton.contains(el) };
|
|
357
|
+
|
|
358
|
+
// Position panel below trigger on desktop
|
|
359
|
+
this.positionPanel();
|
|
360
|
+
|
|
361
|
+
// Bind panel events after creation
|
|
362
|
+
this.bindPanelEvents();
|
|
363
|
+
|
|
364
|
+
// Reposition on resize
|
|
365
|
+
this.addListener(window, 'resize', () => this.positionPanel());
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Position the panel below the trigger button on desktop
|
|
370
|
+
*/
|
|
371
|
+
positionPanel: function () {
|
|
372
|
+
if (!this.elements.panel || !this.elements.trigger) return;
|
|
373
|
+
|
|
374
|
+
const isMobile = window.innerWidth < 768;
|
|
375
|
+
|
|
376
|
+
if (isMobile) {
|
|
377
|
+
// Mobile: full height slide-in from right - let CSS handle it
|
|
378
|
+
this.elements.panel.style.top = '';
|
|
379
|
+
this.elements.panel.style.right = '';
|
|
380
|
+
this.elements.panel.style.left = '';
|
|
381
|
+
this.elements.panel.style.height = '';
|
|
382
|
+
this.elements.panel.style.maxHeight = '';
|
|
383
|
+
} else {
|
|
384
|
+
// Desktop: position directly below the trigger button, aligned to its right edge
|
|
385
|
+
const triggerRect = this.elements.trigger.getBoundingClientRect();
|
|
386
|
+
const panelWidth = 320; // --customizer-width
|
|
387
|
+
const panelTop = triggerRect.bottom + 8;
|
|
388
|
+
|
|
389
|
+
// Calculate right position: align panel's right edge with trigger's right edge
|
|
390
|
+
// But ensure it doesn't overflow the viewport
|
|
391
|
+
const viewportWidth = window.innerWidth;
|
|
392
|
+
let panelRight = viewportWidth - triggerRect.right;
|
|
393
|
+
|
|
394
|
+
// Ensure panel doesn't overflow left side of viewport
|
|
395
|
+
const panelLeft = viewportWidth - panelRight - panelWidth;
|
|
396
|
+
if (panelLeft < 8) {
|
|
397
|
+
panelRight = viewportWidth - panelWidth - 8;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.elements.panel.style.top = panelTop + 'px';
|
|
401
|
+
this.elements.panel.style.right = panelRight + 'px';
|
|
402
|
+
this.elements.panel.style.left = '';
|
|
403
|
+
this.elements.panel.style.height = 'auto';
|
|
404
|
+
this.elements.panel.style.maxHeight = 'calc(100vh - ' + panelTop + 'px)';
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Bind events specifically for the panel (called after dynamic creation)
|
|
410
|
+
*/
|
|
411
|
+
bindPanelEvents: function () {
|
|
412
|
+
if (!this.elements.panel) return;
|
|
413
|
+
if (this.elements.panel.getAttribute('data-customizer-initialized') === 'true') return;
|
|
414
|
+
|
|
415
|
+
this.elements.panel.setAttribute('data-customizer-initialized', 'true');
|
|
416
|
+
|
|
417
|
+
// Primary color swatches
|
|
418
|
+
this.elements.panel.querySelectorAll('[data-color]').forEach(swatch => {
|
|
419
|
+
this.addListener(swatch, 'click', () => {
|
|
420
|
+
this.applyPrimary(swatch.dataset.color);
|
|
421
|
+
this.updateUI();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Neutral color swatches
|
|
426
|
+
this.elements.panel.querySelectorAll('[data-neutral]').forEach(swatch => {
|
|
427
|
+
this.addListener(swatch, 'click', () => {
|
|
428
|
+
this.applyNeutral(swatch.dataset.neutral);
|
|
429
|
+
this.updateUI();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Radius buttons
|
|
434
|
+
this.elements.panel.querySelectorAll('[data-radius]').forEach(btn => {
|
|
435
|
+
this.addListener(btn, 'click', () => {
|
|
436
|
+
this.applyRadius(btn.dataset.radius);
|
|
437
|
+
this.updateUI();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Font selector
|
|
442
|
+
const fontSelect = this.elements.panel.querySelector('[data-customizer-font]');
|
|
443
|
+
if (fontSelect) {
|
|
444
|
+
this.addListener(fontSelect, 'change', (e) => {
|
|
445
|
+
this.applyFont(e.target.value);
|
|
446
|
+
this.updateUI();
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Mode buttons
|
|
451
|
+
this.elements.panel.querySelectorAll('[data-mode]').forEach(btn => {
|
|
452
|
+
this.addListener(btn, 'click', () => {
|
|
453
|
+
this.applyTheme(btn.dataset.mode);
|
|
454
|
+
this.updateUI();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Reset button
|
|
459
|
+
const resetBtn = this.elements.panel.querySelector('.customizer-reset');
|
|
460
|
+
if (resetBtn) {
|
|
461
|
+
this.addListener(resetBtn, 'click', () => {
|
|
462
|
+
this.reset();
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Mobile close button
|
|
467
|
+
const closeBtn = this.elements.panel.querySelector('.customizer-mobile-close');
|
|
468
|
+
if (closeBtn) {
|
|
469
|
+
this.addListener(closeBtn, 'click', () => {
|
|
470
|
+
this.close();
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Overlay click
|
|
475
|
+
if (this.elements.overlay) {
|
|
476
|
+
this.addListener(this.elements.overlay, 'click', () => {
|
|
477
|
+
this.close();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Generate panel HTML
|
|
484
|
+
*/
|
|
485
|
+
getPanelHTML: function () {
|
|
486
|
+
// Generate primary color swatches
|
|
487
|
+
let primarySwatches = '';
|
|
488
|
+
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>`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Generate neutral color swatches
|
|
493
|
+
let neutralSwatches = '';
|
|
494
|
+
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>`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Generate radius buttons
|
|
499
|
+
let radiusButtons = '';
|
|
500
|
+
this.RADIUS_OPTIONS.forEach(r => {
|
|
501
|
+
radiusButtons += `<button class="tc-radius-btn${r === this.state.radius ? ' is-active' : ''}" data-radius="${r}">${r}</button>`;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Generate font options
|
|
505
|
+
let fontOptions = '';
|
|
506
|
+
for (const [key, value] of Object.entries(this.FONT_OPTIONS)) {
|
|
507
|
+
fontOptions += `<option value="${key}"${key === this.state.font ? ' selected' : ''}>${value.name}</option>`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Generate mode buttons
|
|
511
|
+
const modeIcons = {
|
|
512
|
+
'system': 'ph-desktop',
|
|
513
|
+
'dark': 'ph-moon',
|
|
514
|
+
'light': 'ph-sun'
|
|
515
|
+
};
|
|
516
|
+
let modeButtons = '';
|
|
517
|
+
this.THEME_MODES.forEach(mode => {
|
|
518
|
+
modeButtons += `<button class="tc-mode-btn${mode === this.state.theme ? ' is-active' : ''}" data-mode="${mode}"><i class="ph ${modeIcons[mode]}"></i><span>${mode.charAt(0).toUpperCase() + mode.slice(1)}</span></button>`;
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
return `
|
|
522
|
+
<div class="tc-header">
|
|
523
|
+
<h3 class="tc-title">Customize Theme</h3>
|
|
524
|
+
<button class="customizer-mobile-close" aria-label="Close">
|
|
525
|
+
<i class="ph ph-x"></i>
|
|
526
|
+
</button>
|
|
527
|
+
</div>
|
|
528
|
+
<div class="tc-body">
|
|
529
|
+
<div class="tc-section">
|
|
530
|
+
<label class="tc-label">Color Mode</label>
|
|
531
|
+
<div class="tc-mode-group">
|
|
532
|
+
${modeButtons}
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="tc-section">
|
|
536
|
+
<label class="tc-label">Primary Color</label>
|
|
537
|
+
<div class="tc-color-grid">
|
|
538
|
+
${primarySwatches}
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
<div class="tc-section">
|
|
542
|
+
<label class="tc-label">Neutral Color</label>
|
|
543
|
+
<div class="tc-neutral-grid">
|
|
544
|
+
${neutralSwatches}
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="tc-section">
|
|
548
|
+
<label class="tc-label">Border Radius</label>
|
|
549
|
+
<div class="tc-radius-group">
|
|
550
|
+
${radiusButtons}
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
<div class="tc-section">
|
|
554
|
+
<label class="tc-label">Font Family</label>
|
|
555
|
+
<select class="tc-font-select" data-customizer-font>
|
|
556
|
+
${fontOptions}
|
|
557
|
+
</select>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="tc-footer">
|
|
561
|
+
<button class="customizer-reset btn btn-sm btn-outline">Reset to Defaults</button>
|
|
562
|
+
</div>
|
|
563
|
+
`;
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Bind event listeners
|
|
568
|
+
*/
|
|
569
|
+
/**
|
|
570
|
+
* Check whether the current primary color is one of the auto-defaults
|
|
571
|
+
* (i.e. the user hasn't explicitly picked a non-default color).
|
|
572
|
+
*/
|
|
573
|
+
isUsingDefaultPrimary: function () {
|
|
574
|
+
return this.state.primary === this.DEFAULTS.PRIMARY_LIGHT ||
|
|
575
|
+
this.state.primary === this.DEFAULTS.PRIMARY_DARK;
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
bindEvents: function () {
|
|
579
|
+
// Trigger click - bind to any trigger button
|
|
580
|
+
if (this.elements.trigger) {
|
|
581
|
+
this.addListener(this.elements.trigger, 'click', (e) => {
|
|
582
|
+
e.preventDefault();
|
|
583
|
+
e.stopPropagation();
|
|
584
|
+
this.toggle();
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
this.bindPanelEvents();
|
|
589
|
+
|
|
590
|
+
// Listen for OS dark/light changes so we can swap the default primary
|
|
591
|
+
if (window.matchMedia) {
|
|
592
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
593
|
+
const handler = () => {
|
|
594
|
+
if (this.state.theme === 'system' && this.isUsingDefaultPrimary()) {
|
|
595
|
+
const newDefault = this.getDefaultPrimary('system');
|
|
596
|
+
if (newDefault !== this.state.primary) {
|
|
597
|
+
this.applyPrimary(newDefault);
|
|
598
|
+
this.updateUI();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
mq.addEventListener('change', handler);
|
|
603
|
+
this._cleanup.push(() => mq.removeEventListener('change', handler));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Close on outside click
|
|
607
|
+
this.addListener(document, 'click', (e) => {
|
|
608
|
+
if (this.state.isOpen && this.elements.customizer && !this.elements.customizer.contains(e.target)) {
|
|
609
|
+
this.close();
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// Close on Escape key
|
|
614
|
+
this.addListener(document, 'keydown', (e) => {
|
|
615
|
+
if (e.key === 'Escape' && this.state.isOpen) {
|
|
616
|
+
this.close();
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Toggle panel open/close
|
|
623
|
+
*/
|
|
624
|
+
toggle: function () {
|
|
625
|
+
if (this.state.isOpen) {
|
|
626
|
+
this.close();
|
|
627
|
+
} else {
|
|
628
|
+
this.open();
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Open the panel
|
|
634
|
+
*/
|
|
635
|
+
open: function () {
|
|
636
|
+
this.state.isOpen = true;
|
|
637
|
+
|
|
638
|
+
// Ensure panel is positioned correctly before opening
|
|
639
|
+
this.positionPanel();
|
|
640
|
+
|
|
641
|
+
if (this.elements.panel) {
|
|
642
|
+
this.elements.panel.classList.add('is-open');
|
|
643
|
+
}
|
|
644
|
+
if (this.elements.trigger) {
|
|
645
|
+
this.elements.trigger.setAttribute('aria-expanded', 'true');
|
|
646
|
+
}
|
|
647
|
+
if (this.elements.overlay) {
|
|
648
|
+
this.elements.overlay.classList.add('is-active');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
this.dispatchEvent('panel-open', { isOpen: true });
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Close the panel
|
|
656
|
+
*/
|
|
657
|
+
close: function () {
|
|
658
|
+
this.state.isOpen = false;
|
|
659
|
+
|
|
660
|
+
if (this.elements.panel) {
|
|
661
|
+
this.elements.panel.classList.remove('is-open');
|
|
662
|
+
}
|
|
663
|
+
if (this.elements.trigger) {
|
|
664
|
+
this.elements.trigger.setAttribute('aria-expanded', 'false');
|
|
665
|
+
}
|
|
666
|
+
if (this.elements.overlay) {
|
|
667
|
+
this.elements.overlay.classList.remove('is-active');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
this.dispatchEvent('panel-close', { isOpen: false });
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Update UI to reflect current state
|
|
675
|
+
*/
|
|
676
|
+
updateUI: function () {
|
|
677
|
+
if (!this.elements.panel) return;
|
|
678
|
+
|
|
679
|
+
// Update primary color swatches
|
|
680
|
+
this.elements.panel.querySelectorAll('[data-color]').forEach(swatch => {
|
|
681
|
+
swatch.classList.toggle('is-active', swatch.dataset.color === this.state.primary);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Update neutral color swatches
|
|
685
|
+
this.elements.panel.querySelectorAll('[data-neutral]').forEach(swatch => {
|
|
686
|
+
swatch.classList.toggle('is-active', swatch.dataset.neutral === this.state.neutral);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Update radius buttons
|
|
690
|
+
this.elements.panel.querySelectorAll('[data-radius]').forEach(btn => {
|
|
691
|
+
btn.classList.toggle('is-active', btn.dataset.radius === this.state.radius);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// Update font selector
|
|
695
|
+
const fontSelect = this.elements.panel.querySelector('[data-customizer-font]');
|
|
696
|
+
if (fontSelect) {
|
|
697
|
+
fontSelect.value = this.state.font;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Update mode buttons
|
|
701
|
+
this.elements.panel.querySelectorAll('[data-mode]').forEach(btn => {
|
|
702
|
+
btn.classList.toggle('is-active', btn.dataset.mode === this.state.theme);
|
|
703
|
+
});
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Reset all preferences to defaults
|
|
708
|
+
*/
|
|
709
|
+
reset: function () {
|
|
710
|
+
this.applyTheme(this.DEFAULTS.THEME);
|
|
711
|
+
this.applyPrimary(this.getDefaultPrimary(this.DEFAULTS.THEME));
|
|
712
|
+
this.applyNeutral(this.DEFAULTS.NEUTRAL);
|
|
713
|
+
this.applyRadius(this.DEFAULTS.RADIUS);
|
|
714
|
+
this.applyFont(this.DEFAULTS.FONT);
|
|
715
|
+
this.applyTheme(this.DEFAULTS.THEME);
|
|
716
|
+
this.updateUI();
|
|
717
|
+
|
|
718
|
+
this.dispatchEvent('reset', { state: { ...this.state } });
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Get current state
|
|
723
|
+
*/
|
|
724
|
+
getState: function () {
|
|
725
|
+
return { ...this.state };
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Programmatically set preferences
|
|
730
|
+
*/
|
|
731
|
+
setPreferences: function (prefs) {
|
|
732
|
+
if (prefs.primary) this.applyPrimary(prefs.primary);
|
|
733
|
+
if (prefs.neutral) this.applyNeutral(prefs.neutral);
|
|
734
|
+
if (prefs.radius) this.applyRadius(prefs.radius);
|
|
735
|
+
if (prefs.font) this.applyFont(prefs.font);
|
|
736
|
+
if (prefs.theme) this.applyTheme(prefs.theme);
|
|
737
|
+
this.updateUI();
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
getStorageValue: function (key, fallback) {
|
|
741
|
+
if (typeof window.safeStorageGet === 'function') {
|
|
742
|
+
return window.safeStorageGet(key, fallback);
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
const value = localStorage.getItem(key);
|
|
746
|
+
return value !== null ? value : fallback;
|
|
747
|
+
} catch (_e) {
|
|
748
|
+
return fallback;
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
setStorageValue: function (key, value) {
|
|
753
|
+
if (typeof window.safeStorageSet === 'function') {
|
|
754
|
+
return window.safeStorageSet(key, value);
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
localStorage.setItem(key, value);
|
|
758
|
+
return true;
|
|
759
|
+
} catch (_e) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
destroyAll: function () {
|
|
765
|
+
this._cleanup.forEach(fn => fn());
|
|
766
|
+
this._cleanup = [];
|
|
767
|
+
|
|
768
|
+
if (this.elements.panel) {
|
|
769
|
+
this.elements.panel.removeAttribute('data-customizer-initialized');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
this.close();
|
|
773
|
+
this.isInitialized = false;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
// Register component
|
|
778
|
+
if (window.Vanduo) {
|
|
779
|
+
window.Vanduo.register('themeCustomizer', ThemeCustomizer);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Expose globally for convenience
|
|
783
|
+
window.ThemeCustomizer = ThemeCustomizer;
|
|
784
|
+
})();
|