kempo-css 1.0.7 → 1.0.9
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/.github/copilot-instructions.md +165 -0
- package/.github/prompts/build-theme-editor.prompt.md +89 -0
- package/LICENSE.md +91 -0
- package/README.md +13 -0
- package/docs/.config.json +17 -0
- package/docs/components/ThemePropertyInput.js +47 -0
- package/docs/demo.inc.html +182 -0
- package/docs/docs.inc.html +823 -0
- package/{examples → docs/examples}/responsive-grid.html +3 -1
- package/docs/index.html +105 -0
- package/docs/init.js +4 -0
- package/docs/kempo-hljs.min.css +1 -0
- package/docs/kempo.min.css +1 -0
- package/docs/manifest.json +87 -0
- package/docs/nav.js +17 -0
- package/docs/theme-editor.html +847 -0
- package/package.json +10 -7
- package/{build.js → scripts/build.js} +75 -2
- package/src/components/ThemePropertyInput.js +154 -0
- package/src/kempo-hljs.css +125 -0
- package/src/kempo.css +1027 -0
- package/index.html +0 -1101
- package/src/ColorEditor.js +0 -26
- package/src/CssVar.js +0 -56
- package/src/lit-all.min.js +0 -120
- package/theme-builder.html +0 -286
- /package/{kempo-hljs.css → docs/kempo-hljs.css} +0 -0
- /package/{kempo.css → docs/kempo.css} +0 -0
- /package/{media → docs/media}/hexagon.svg +0 -0
- /package/{media → docs/media}/icon-maskable.png +0 -0
- /package/{media → docs/media}/icon.svg +0 -0
- /package/{media → docs/media}/icon128.png +0 -0
- /package/{media → docs/media}/icon144.png +0 -0
- /package/{media → docs/media}/icon152.png +0 -0
- /package/{media → docs/media}/icon16-48.svg +0 -0
- /package/{media → docs/media}/icon16.png +0 -0
- /package/{media → docs/media}/icon192.png +0 -0
- /package/{media → docs/media}/icon256.png +0 -0
- /package/{media → docs/media}/icon32.png +0 -0
- /package/{media → docs/media}/icon384.png +0 -0
- /package/{media → docs/media}/icon48.png +0 -0
- /package/{media → docs/media}/icon512.png +0 -0
- /package/{media → docs/media}/icon64.png +0 -0
- /package/{media → docs/media}/icon72.png +0 -0
- /package/{media → docs/media}/icon96.png +0 -0
- /package/{media → docs/media}/kempo-fist.svg +0 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Kempo CSS</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="48x48" href="./media/icon48.png">
|
|
8
|
+
<link rel="manifest" href="./manifest.json" />
|
|
9
|
+
<link rel="stylesheet" href="./kempo.min.css" />
|
|
10
|
+
<link rel="stylesheet" href="./kempo-hljs.min.css" />
|
|
11
|
+
<script src="./init.js" type="module"></script>
|
|
12
|
+
<style>
|
|
13
|
+
html {
|
|
14
|
+
scrollbar-gutter: unset;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#grid-container {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-rows: auto 1fr;
|
|
27
|
+
height: 100vh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
k-split {
|
|
31
|
+
height: 100%;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<div id="grid-container">
|
|
37
|
+
<nav class="d-f bg-primary">
|
|
38
|
+
<button id="toggleNavSideMenu" class="link">
|
|
39
|
+
<k-icon name="menu"></k-icon>
|
|
40
|
+
</button>
|
|
41
|
+
<a href="./" class="d-if ph" style="align-items: center">
|
|
42
|
+
<img src="./media/icon32.png" alt="Kempo CSS Icon" class="pr" />
|
|
43
|
+
Kempo CSS
|
|
44
|
+
</a>
|
|
45
|
+
<a href="./theme-editor.html">Theme Editor</a>
|
|
46
|
+
<div class="flex"></div>
|
|
47
|
+
<a href="https://github.com/dustinpoissant/kempo-css?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
|
|
48
|
+
target="_blank"><k-icon name="license"></k-icon></a>
|
|
49
|
+
<a href="https://github.com/dustinpoissant/kempo-css" target="_blank"><k-icon name="github-mark"></k-icon></a>
|
|
50
|
+
<k-theme-switcher></k-theme-switcher>
|
|
51
|
+
</nav>
|
|
52
|
+
<k-side-menu id="navSideMenu">
|
|
53
|
+
<menu>
|
|
54
|
+
<div class="ta-center bb mb r0">
|
|
55
|
+
<h1 class="tc-primary">Kempo CSS</h1>
|
|
56
|
+
<h3 class="tc-primary">Theme Builder</h3>
|
|
57
|
+
<img src="./media/icon128.png" alt="Kempo UI Icon" />
|
|
58
|
+
</div>
|
|
59
|
+
<h5><a href="#editColors">Colors</a></h5>
|
|
60
|
+
<hr />
|
|
61
|
+
<a href="https://github.com/dustinpoissant/kempo-css?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
|
|
62
|
+
target="_blank"><k-icon name="license"></k-icon> Read the Lisence</a><br />
|
|
63
|
+
<a href="https://github.com/dustinpoissant/kempo-css" target="_blank"><k-icon name="github-mark"></k-icon> View
|
|
64
|
+
on
|
|
65
|
+
GitHub</a>
|
|
66
|
+
<div style="height:33vh"></div>
|
|
67
|
+
</menu>
|
|
68
|
+
</k-side-menu>
|
|
69
|
+
<k-split style="--left_width: 24rem;">
|
|
70
|
+
<aside class="d-b p" style="overflow-y: auto; height: 100%;">
|
|
71
|
+
<div class="mb">
|
|
72
|
+
<div class="d-f full btn-grp mb">
|
|
73
|
+
<button id="downloadTheme" class="primary flex">Download Theme</button>
|
|
74
|
+
<button id="uploadThemeBtn" class="secondary flex">Upload Theme</button>
|
|
75
|
+
</div>
|
|
76
|
+
<label class="d-b mb-q">
|
|
77
|
+
<strong>Editing Theme</strong>
|
|
78
|
+
<select id="editingThemeSelect" class="w-100">
|
|
79
|
+
<option value="light">Light</option>
|
|
80
|
+
<option value="dark">Dark</option>
|
|
81
|
+
</select>
|
|
82
|
+
</label>
|
|
83
|
+
</div>
|
|
84
|
+
<hr class="mb">
|
|
85
|
+
<div id="themeInputs"></div>
|
|
86
|
+
</aside>
|
|
87
|
+
<div slot="right" style="overflow-y: auto; height: 100%;">
|
|
88
|
+
<main style="padding: var(--spacer);">
|
|
89
|
+
<k-import src="./demo.inc.html"></k-import>
|
|
90
|
+
</main>
|
|
91
|
+
</div>
|
|
92
|
+
</k-split>
|
|
93
|
+
</div>
|
|
94
|
+
<script src="./nav.js" type="module"></script>
|
|
95
|
+
<script src="./kempo-ui/components/SideMenu.js" type="module"></script>
|
|
96
|
+
<script src="./kempo-ui/components/Split.js" type="module"></script>
|
|
97
|
+
<script src="./kempo-ui/components/Icon.js" type="module"></script>
|
|
98
|
+
<script src="./kempo-ui/components/ThemeSwitcher.js" type="module"></script>
|
|
99
|
+
<script src="./kempo-ui/components/Import.js" type="module"></script>
|
|
100
|
+
<script src="./kempo-ui/components/Resize.js" type="module"></script>
|
|
101
|
+
<script src="./kempo-ui/components/Card.js" type="module"></script>
|
|
102
|
+
<script src="./kempo-ui/components/ColorPicker.js" type="module"></script>
|
|
103
|
+
<script src="./kempo-ui/components/Dialog.js" type="module"></script>
|
|
104
|
+
<script src="./components/ThemePropertyInput.js" type="module"></script>
|
|
105
|
+
<script type="module">
|
|
106
|
+
import theme from '/kempo-ui/utils/theme.js';
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
Simple Context for storing theme CSS
|
|
110
|
+
*/
|
|
111
|
+
const themeContext = {};
|
|
112
|
+
const setContext = (key, value) => { themeContext[key] = value; };
|
|
113
|
+
const getContext = key => themeContext[key];
|
|
114
|
+
|
|
115
|
+
const defaultTheme = {
|
|
116
|
+
"--ff_body": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
|
|
117
|
+
"--ff_heading": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
|
|
118
|
+
"--ff_mono": "Consolas, monaco, monospace",
|
|
119
|
+
"--fs_base": "16px",
|
|
120
|
+
"--fs_small": "calc(0.6 * var(--fs_base))",
|
|
121
|
+
"--fs_large": "calc(1.5 * var(--fs_base))",
|
|
122
|
+
"--fs_h6": "var(--fs_base)",
|
|
123
|
+
"--fs_h5": "calc(1.25 * var(--fs_base))",
|
|
124
|
+
"--fs_h4": "calc(1.5 * var(--fs_base))",
|
|
125
|
+
"--fs_h3": "calc(1.75 * var(--fs_base))",
|
|
126
|
+
"--fs_h2": "calc(2 * var(--fs_base))",
|
|
127
|
+
"--fs_h1": "calc(2.5 * var(--fs_base))",
|
|
128
|
+
"--fw_base": "400",
|
|
129
|
+
"--fw_bold": "700",
|
|
130
|
+
"--spacer": "1rem",
|
|
131
|
+
"--spacer_h": "calc(0.5 * var(--spacer))",
|
|
132
|
+
"--spacer_q": "calc(0.25 * var(--spacer))",
|
|
133
|
+
"--line-height": "1.35em",
|
|
134
|
+
"--container_width": "90rem",
|
|
135
|
+
"--animation_ms": "256ms",
|
|
136
|
+
"--radius": "0.25rem",
|
|
137
|
+
"--link_decoration": "underline",
|
|
138
|
+
"--input_padding": "var(--spacer_h) var(--spacer)",
|
|
139
|
+
"--input_border_width": "1px",
|
|
140
|
+
"--btn_padding": "var(--spacer_h) var(--spacer)",
|
|
141
|
+
"--c_bg": { light: "rgb(249, 249, 249)", dark: "rgb(51, 51, 51)" },
|
|
142
|
+
"--c_bg__inv": { light: "rgb(51, 51, 51)", dark: "rgb(249, 249, 249)" },
|
|
143
|
+
"--c_bg__alt": { light: "rgb(238, 238, 238)", dark: "rgb(34, 34, 34)" },
|
|
144
|
+
"--c_overscroll": { light: "rgb(255, 255, 255)", dark: "rgb(0, 0, 0)" },
|
|
145
|
+
"--c_border": { light: "rgb(204, 204, 204)", dark: "rgb(119, 119, 119)" },
|
|
146
|
+
"--c_border__inv": { light: "var(--d_c_bg_border)", dark: "rgb(204, 204, 204)" },
|
|
147
|
+
"--c_primary": "rgb(51, 102, 255)",
|
|
148
|
+
"--c_primary__hover": "rgb(17, 68, 221)",
|
|
149
|
+
"--c_secondary": "rgb(153, 51, 255)",
|
|
150
|
+
"--c_secondary__hover": "rgb(119, 17, 221)",
|
|
151
|
+
"--c_success": "rgb(0, 136, 0)",
|
|
152
|
+
"--c_success__hover": "rgb(0, 102, 0)",
|
|
153
|
+
"--c_warning": "rgb(255, 102, 0)",
|
|
154
|
+
"--c_warning__hover": "rgb(221, 68, 0)",
|
|
155
|
+
"--c_danger": "rgb(255, 0, 51)",
|
|
156
|
+
"--c_danger__hover": "rgb(221, 0, 17)",
|
|
157
|
+
"--c_input_accent": "rgb(51, 102, 255)",
|
|
158
|
+
"--c_input_border": "var(--c_border)",
|
|
159
|
+
"--c_highlight": { light: "rgba(41, 100, 210, 0.25)", dark: "rgba(0, 89, 255, 0.25)" },
|
|
160
|
+
"--tc": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
161
|
+
"--tc_dark": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
162
|
+
"--tc_light": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
163
|
+
"--tc_inv": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
164
|
+
"--tc_muted": { light: "rgba(0, 0, 0, 0.5)", dark: "rgba(255, 255, 255, 0.5)" },
|
|
165
|
+
"--tc_on_primary": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
166
|
+
"--tc_on_secondary": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
167
|
+
"--tc_on_success": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
168
|
+
"--tc_on_warning": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
169
|
+
"--tc_on_danger": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
170
|
+
"--c_overlay": "rgba(0, 0, 0, 0.5)",
|
|
171
|
+
"--tc_primary": { light: "#36f", dark: "rgb(138, 180, 248)" },
|
|
172
|
+
"--tc_secondary": { light: "#93f", dark: "rgb(187, 102, 255)" },
|
|
173
|
+
"--tc_success": { light: "#080", dark: "rgb(102, 187, 102)" },
|
|
174
|
+
"--tc_warning": { light: "#f60", dark: "rgb(255, 153, 51)" },
|
|
175
|
+
"--tc_danger": { light: "#f03", dark: "rgb(255, 85, 119)" },
|
|
176
|
+
"--btn_box_shadow": "0 0 0 transparent",
|
|
177
|
+
"--btn_box_shadow__hover": "0 0 0 transparent",
|
|
178
|
+
"--btn_border": "transparent",
|
|
179
|
+
"--btn_bg": { light: "rgb(221, 221, 221)", dark: "rgb(170, 170, 170)" },
|
|
180
|
+
"--btn_bg__hover": { light: "rgb(204, 204, 204)", dark: "rgb(187, 187, 187)" },
|
|
181
|
+
"--btn_tc": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
182
|
+
"--btn_transparent__hover": { light: "rgba(0, 0, 0, 0.05)", dark: "rgba(255, 255, 255, 0.05)" },
|
|
183
|
+
"--tc_link": "var(--tc_primary)",
|
|
184
|
+
"--tc_link__hover": "var(--tc_secondary)",
|
|
185
|
+
"--tc_link__inv": "var(--tc_primary__inv)",
|
|
186
|
+
"--tc_link__inv__hover": "var(--tc_secondary__inv)",
|
|
187
|
+
"--focus_shadow": "0 0 2px 2px var(--c_primary)",
|
|
188
|
+
"--focus_shadow_on_primary": "0 0 2px 2px var(--tc_on_primary)",
|
|
189
|
+
"--input_bg": { light: "white", dark: "var(--c_bg__alt)" },
|
|
190
|
+
"--input_tc": { light: "rgba(0, 0, 0, 0.93)", dark: "var(--tc)" },
|
|
191
|
+
"--drop_shadow": { light: "0 0.25rem 0.5rem rgba(0, 0, 0, 0.333)", dark: "0 0.25rem 0.5rem rgba(0, 0, 0, 0.25)" },
|
|
192
|
+
"--date_picker_icon_filter": { light: "invert(0)", dark: "invert(1)" }
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
Current Theme State
|
|
197
|
+
*/
|
|
198
|
+
const currentTheme = { light: {}, dark: {} };
|
|
199
|
+
const editingTheme = { value: theme.getCalculated() };
|
|
200
|
+
const availableProperties = Object.keys(defaultTheme);
|
|
201
|
+
|
|
202
|
+
/*
|
|
203
|
+
Initialize Theme State
|
|
204
|
+
*/
|
|
205
|
+
Object.entries(defaultTheme).forEach(([prop, value]) => {
|
|
206
|
+
if(typeof value === 'object' && value.light){
|
|
207
|
+
currentTheme.light[prop] = value.light;
|
|
208
|
+
currentTheme.dark[prop] = value.dark;
|
|
209
|
+
}else{
|
|
210
|
+
currentTheme.light[prop] = value;
|
|
211
|
+
currentTheme.dark[prop] = value;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
Render Theme Inputs
|
|
217
|
+
*/
|
|
218
|
+
const themeInputsContainer = document.getElementById('themeInputs');
|
|
219
|
+
|
|
220
|
+
const propertyCategories = [
|
|
221
|
+
{
|
|
222
|
+
title: 'Theme Colors',
|
|
223
|
+
props: [
|
|
224
|
+
'--c_primary', '--c_primary__hover',
|
|
225
|
+
'--c_secondary', '--c_secondary__hover',
|
|
226
|
+
'--c_success', '--c_success__hover',
|
|
227
|
+
'--c_warning', '--c_warning__hover',
|
|
228
|
+
'--c_danger', '--c_danger__hover'
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
title: 'Background Colors',
|
|
233
|
+
props: [
|
|
234
|
+
'--c_bg', '--c_bg__inv', '--c_bg__alt', '--c_overscroll'
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
title: 'Text Colors',
|
|
239
|
+
props: [
|
|
240
|
+
'--tc', '--tc_dark', '--tc_light', '--tc_inv', '--tc_muted',
|
|
241
|
+
'--tc_on_primary', '--tc_on_secondary', '--tc_on_success', '--tc_on_warning', '--tc_on_danger',
|
|
242
|
+
'--tc_primary', '--tc_secondary', '--tc_success', '--tc_warning', '--tc_danger'
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
title: 'Border Colors',
|
|
247
|
+
props: [
|
|
248
|
+
'--c_border', '--c_border__inv'
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
title: 'Link Colors',
|
|
253
|
+
props: [
|
|
254
|
+
'--tc_link', '--tc_link__hover', '--tc_link__inv', '--tc_link__inv__hover'
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
title: 'Button Styles',
|
|
259
|
+
props: [
|
|
260
|
+
'--btn_bg', '--btn_bg__hover', '--btn_tc', '--btn_transparent__hover',
|
|
261
|
+
'--btn_border', '--btn_box_shadow', '--btn_box_shadow__hover'
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
title: 'Input Styles',
|
|
266
|
+
props: [
|
|
267
|
+
'--input_bg', '--input_tc', '--c_input_accent', '--c_input_border'
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
title: 'Focus & Effects',
|
|
272
|
+
props: [
|
|
273
|
+
'--focus_shadow', '--focus_shadow_on_primary',
|
|
274
|
+
'--drop_shadow', '--date_picker_icon_filter',
|
|
275
|
+
'--c_highlight', '--c_overlay'
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
title: 'Typography',
|
|
280
|
+
props: [
|
|
281
|
+
'--ff_body', '--ff_heading', '--ff_mono',
|
|
282
|
+
'--fs_base', '--fs_small', '--fs_large',
|
|
283
|
+
'--fs_h1', '--fs_h2', '--fs_h3', '--fs_h4', '--fs_h5', '--fs_h6',
|
|
284
|
+
'--fw_base', '--fw_bold'
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
title: 'Spacing & Layout',
|
|
289
|
+
props: [
|
|
290
|
+
'--spacer', '--line-height', '--container_width'
|
|
291
|
+
]
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
title: 'Effects & Animation',
|
|
295
|
+
props: [
|
|
296
|
+
'--animation_ms', '--radius', '--link_decoration',
|
|
297
|
+
'--input_padding', '--input_border_width', '--btn_padding'
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const isColorValue = (value) => {
|
|
303
|
+
if(!value || typeof value !== 'string') return false;
|
|
304
|
+
if(value.startsWith('var(')) return false;
|
|
305
|
+
if(value.startsWith('calc(')) return false;
|
|
306
|
+
if(value.includes('px') || value.includes('rem') || value.includes('em')) return false;
|
|
307
|
+
if(value.includes('ms') || value.includes('s')) return false;
|
|
308
|
+
if(/^[\d.]+$/.test(value)) return false;
|
|
309
|
+
if(value.startsWith('"') || value.startsWith("'")) return false;
|
|
310
|
+
return value.startsWith('#') ||
|
|
311
|
+
value.startsWith('rgb') ||
|
|
312
|
+
value.startsWith('hsl') ||
|
|
313
|
+
value.startsWith('hwb') ||
|
|
314
|
+
value.startsWith('lab') ||
|
|
315
|
+
value.startsWith('lch') ||
|
|
316
|
+
value.startsWith('oklab') ||
|
|
317
|
+
value.startsWith('oklch') ||
|
|
318
|
+
value === 'transparent' ||
|
|
319
|
+
value.includes('light-dark');
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const normalizeColor = (color) => {
|
|
323
|
+
if(!color || typeof color !== 'string') return color;
|
|
324
|
+
if(color.startsWith('var(')) return color;
|
|
325
|
+
if(!isColorValue(color)) return color;
|
|
326
|
+
const testEl = document.createElement('div');
|
|
327
|
+
testEl.style.color = color;
|
|
328
|
+
document.body.appendChild(testEl);
|
|
329
|
+
const computed = window.getComputedStyle(testEl).color;
|
|
330
|
+
document.body.removeChild(testEl);
|
|
331
|
+
return computed || color;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const colorsEqual = (color1, color2) => {
|
|
335
|
+
if(color1 === color2) return true;
|
|
336
|
+
if(!isColorValue(color1) || !isColorValue(color2)) return color1 === color2;
|
|
337
|
+
return normalizeColor(color1) === normalizeColor(color2);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const getComputedValue = (varName) => {
|
|
341
|
+
const testEl = document.createElement('div');
|
|
342
|
+
testEl.style.color = varName;
|
|
343
|
+
document.body.appendChild(testEl);
|
|
344
|
+
const computed = window.getComputedStyle(testEl).color;
|
|
345
|
+
document.body.removeChild(testEl);
|
|
346
|
+
return computed;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const isColorProperty = (prop) => {
|
|
350
|
+
return prop.startsWith('--c_') || prop.startsWith('--tc_') || prop === '--tc' || (prop.startsWith('--btn_') && !prop.includes('shadow'));
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const isFontWeightProperty = (prop) => {
|
|
354
|
+
return prop.startsWith('--fw_');
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const propToLabel = (prop) => {
|
|
358
|
+
const withoutPrefix = prop.replace(/^--/, '');
|
|
359
|
+
const words = withoutPrefix.split('_').filter(word => {
|
|
360
|
+
return word !== 'c' && word !== 'tc';
|
|
361
|
+
}).map(word => {
|
|
362
|
+
const abbrevMap = {
|
|
363
|
+
'bg': 'Background', 'inv': 'Inverse',
|
|
364
|
+
'ff': 'Font Family', 'fs': 'Font Size', 'fw': 'Font Weight',
|
|
365
|
+
'btn': 'Button', 'h1': 'H1', 'h2': 'H2', 'h3': 'H3', 'h4': 'H4', 'h5': 'H5', 'h6': 'H6',
|
|
366
|
+
'ms': 'Duration', 'h': 'Half', 'q': 'Quarter'
|
|
367
|
+
};
|
|
368
|
+
if(abbrevMap[word]) return abbrevMap[word];
|
|
369
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
370
|
+
});
|
|
371
|
+
return words.join(' ');
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
propertyCategories.forEach(({ title, props }) => {
|
|
375
|
+
const section = document.createElement('div');
|
|
376
|
+
section.className = 'mb';
|
|
377
|
+
|
|
378
|
+
const heading = document.createElement('h4');
|
|
379
|
+
heading.className = 'mb-h';
|
|
380
|
+
heading.textContent = title;
|
|
381
|
+
section.appendChild(heading);
|
|
382
|
+
|
|
383
|
+
props.forEach(prop => {
|
|
384
|
+
if(defaultTheme[prop] === undefined) return;
|
|
385
|
+
|
|
386
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
387
|
+
|
|
388
|
+
if(isColorProperty(prop)){
|
|
389
|
+
const input = document.createElement('k-theme-property-input');
|
|
390
|
+
input.setAttribute('prop-name', prop);
|
|
391
|
+
input.setAttribute('label', propToLabel(prop));
|
|
392
|
+
input.setAttribute('available-properties', JSON.stringify(availableProperties));
|
|
393
|
+
input.setAttribute('value', value);
|
|
394
|
+
|
|
395
|
+
if(isColorValue(value)){
|
|
396
|
+
input.setAttribute('mode', 'color');
|
|
397
|
+
}else{
|
|
398
|
+
input.setAttribute('mode', 'var');
|
|
399
|
+
if(value.startsWith('var(')){
|
|
400
|
+
const computedColor = getComputedValue(value);
|
|
401
|
+
if(computedColor && computedColor !== 'rgba(0, 0, 0, 0)' && !computedColor.includes('NaN')){
|
|
402
|
+
input.setAttribute('initial-color', computedColor);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
input.className = 'mb';
|
|
408
|
+
section.appendChild(input);
|
|
409
|
+
}else if(isFontWeightProperty(prop)){
|
|
410
|
+
const wrapper = document.createElement('div');
|
|
411
|
+
wrapper.className = 'mb';
|
|
412
|
+
|
|
413
|
+
const label = document.createElement('label');
|
|
414
|
+
label.innerHTML = `${propToLabel(prop)} <small class="tc-muted"><code>${prop}</code></small>`;
|
|
415
|
+
|
|
416
|
+
const input = document.createElement('input');
|
|
417
|
+
input.setAttribute('type', 'number');
|
|
418
|
+
input.setAttribute('data-prop-name', prop);
|
|
419
|
+
input.setAttribute('min', '100');
|
|
420
|
+
input.setAttribute('max', '900');
|
|
421
|
+
input.setAttribute('step', '100');
|
|
422
|
+
input.value = value;
|
|
423
|
+
input.style.fontFamily = 'var(--ff_mono)';
|
|
424
|
+
input.style.fontSize = '0.875rem';
|
|
425
|
+
|
|
426
|
+
wrapper.appendChild(label);
|
|
427
|
+
wrapper.appendChild(input);
|
|
428
|
+
section.appendChild(wrapper);
|
|
429
|
+
}else{
|
|
430
|
+
const wrapper = document.createElement('div');
|
|
431
|
+
wrapper.className = 'mb';
|
|
432
|
+
|
|
433
|
+
const label = document.createElement('label');
|
|
434
|
+
label.innerHTML = `${propToLabel(prop)} <small class="tc-muted"><code>${prop}</code></small>`;
|
|
435
|
+
|
|
436
|
+
const input = document.createElement('input');
|
|
437
|
+
input.setAttribute('type', 'text');
|
|
438
|
+
input.setAttribute('data-prop-name', prop);
|
|
439
|
+
input.value = value;
|
|
440
|
+
input.style.fontFamily = 'var(--ff_mono)';
|
|
441
|
+
input.style.fontSize = '0.875rem';
|
|
442
|
+
|
|
443
|
+
wrapper.appendChild(label);
|
|
444
|
+
wrapper.appendChild(input);
|
|
445
|
+
section.appendChild(wrapper);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
themeInputsContainer.appendChild(section);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
/*
|
|
453
|
+
Handle Theme Switch
|
|
454
|
+
*/
|
|
455
|
+
const editingThemeSelect = document.getElementById('editingThemeSelect');
|
|
456
|
+
editingThemeSelect.value = editingTheme.value;
|
|
457
|
+
|
|
458
|
+
const updateInputValues = () => {
|
|
459
|
+
document.querySelectorAll('k-theme-property-input').forEach(input => {
|
|
460
|
+
const prop = input.getAttribute('prop-name');
|
|
461
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
462
|
+
input.setAttribute('value', value);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
document.querySelectorAll('input[data-prop-name]').forEach(input => {
|
|
466
|
+
const prop = input.getAttribute('data-prop-name');
|
|
467
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
468
|
+
input.value = value;
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
editingThemeSelect.addEventListener('change', () => {
|
|
473
|
+
editingTheme.value = editingThemeSelect.value;
|
|
474
|
+
theme.set(editingTheme.value);
|
|
475
|
+
updateInputValues();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
theme.subscribe(() => {
|
|
479
|
+
editingTheme.value = theme.getCalculated();
|
|
480
|
+
editingThemeSelect.value = editingTheme.value;
|
|
481
|
+
updateInputValues();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
/*
|
|
485
|
+
Change Tracking
|
|
486
|
+
*/
|
|
487
|
+
const getThemeDiff = () => {
|
|
488
|
+
const diff = {};
|
|
489
|
+
|
|
490
|
+
['light', 'dark'].forEach(themeMode => {
|
|
491
|
+
Object.entries(currentTheme[themeMode]).forEach(([prop, value]) => {
|
|
492
|
+
const defaultValue = typeof defaultTheme[prop] === 'object'
|
|
493
|
+
? defaultTheme[prop][themeMode]
|
|
494
|
+
: defaultTheme[prop];
|
|
495
|
+
|
|
496
|
+
const isEqual = isColorProperty(prop)
|
|
497
|
+
? colorsEqual(value, defaultValue)
|
|
498
|
+
: value === defaultValue;
|
|
499
|
+
|
|
500
|
+
if(!isEqual){
|
|
501
|
+
if(!diff[prop]) diff[prop] = {};
|
|
502
|
+
diff[prop][themeMode] = value;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return diff;
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const handlePropertyChange = (prop, value) => {
|
|
511
|
+
currentTheme[editingTheme.value][prop] = value;
|
|
512
|
+
|
|
513
|
+
const diff = getThemeDiff();
|
|
514
|
+
const css = generateThemeCSS(diff);
|
|
515
|
+
applyThemeCSS(css);
|
|
516
|
+
console.log('Theme CSS:', css);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
/*
|
|
520
|
+
CSS Generation
|
|
521
|
+
*/
|
|
522
|
+
const generateThemeCSS = (diff) => {
|
|
523
|
+
if(Object.keys(diff).length === 0) return '';
|
|
524
|
+
|
|
525
|
+
const cssLines = [];
|
|
526
|
+
|
|
527
|
+
Object.entries(diff).forEach(([prop, value]) => {
|
|
528
|
+
if(typeof value === 'object'){
|
|
529
|
+
// Property has light/dark variants
|
|
530
|
+
const hasLight = value.light !== undefined;
|
|
531
|
+
const hasDark = value.dark !== undefined;
|
|
532
|
+
|
|
533
|
+
if(hasLight && hasDark){
|
|
534
|
+
// Both themes changed - use light-dark()
|
|
535
|
+
cssLines.push(` ${prop}: light-dark(${value.light}, ${value.dark});`);
|
|
536
|
+
} else if(hasLight){
|
|
537
|
+
// Only light changed - need to get dark from current or default
|
|
538
|
+
const darkValue = currentTheme.dark[prop] !== undefined
|
|
539
|
+
? currentTheme.dark[prop]
|
|
540
|
+
: (typeof defaultTheme[prop] === 'object' ? defaultTheme[prop].dark : defaultTheme[prop]);
|
|
541
|
+
cssLines.push(` ${prop}: light-dark(${value.light}, ${darkValue});`);
|
|
542
|
+
} else if(hasDark){
|
|
543
|
+
// Only dark changed - need to get light from current or default
|
|
544
|
+
const lightValue = currentTheme.light[prop] !== undefined
|
|
545
|
+
? currentTheme.light[prop]
|
|
546
|
+
: (typeof defaultTheme[prop] === 'object' ? defaultTheme[prop].light : defaultTheme[prop]);
|
|
547
|
+
cssLines.push(` ${prop}: light-dark(${lightValue}, ${value.dark});`);
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
// Single value (non-themed property)
|
|
551
|
+
cssLines.push(` ${prop}: ${value};`);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if(cssLines.length === 0) return '';
|
|
556
|
+
|
|
557
|
+
return `:root {\n${cssLines.join('\n')}\n}`;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const applyThemeCSS = (css) => {
|
|
561
|
+
let styleEl = document.getElementById('custom-theme');
|
|
562
|
+
if(!styleEl){
|
|
563
|
+
styleEl = document.createElement('style');
|
|
564
|
+
styleEl.id = 'custom-theme';
|
|
565
|
+
document.head.appendChild(styleEl);
|
|
566
|
+
}
|
|
567
|
+
styleEl.textContent = css;
|
|
568
|
+
|
|
569
|
+
// Save to context for download
|
|
570
|
+
setContext('customThemeCSS', css);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
/*
|
|
574
|
+
Event Listeners for Changes
|
|
575
|
+
*/
|
|
576
|
+
document.addEventListener('value-change', (e) => {
|
|
577
|
+
const component = e.target;
|
|
578
|
+
if(component.tagName === 'K-THEME-PROPERTY-INPUT'){
|
|
579
|
+
const { propName, value } = e.detail;
|
|
580
|
+
handlePropertyChange(propName, value);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
document.querySelectorAll('input[data-prop-name]').forEach(input => {
|
|
585
|
+
input.addEventListener('input', (e) => {
|
|
586
|
+
const prop = e.target.getAttribute('data-prop-name');
|
|
587
|
+
const value = e.target.value;
|
|
588
|
+
handlePropertyChange(prop, value);
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/*
|
|
593
|
+
Setup color picker change listeners after components render
|
|
594
|
+
Listen directly to native color inputs since k-color-picker doesn't always re-dispatch events
|
|
595
|
+
*/
|
|
596
|
+
const attachColorPickerListeners = () => {
|
|
597
|
+
document.querySelectorAll('k-theme-property-input').forEach(input => {
|
|
598
|
+
const colorPicker = input.shadowRoot?.querySelector('k-color-picker');
|
|
599
|
+
if(!colorPicker) return;
|
|
600
|
+
|
|
601
|
+
// Get the native color input inside k-color-picker
|
|
602
|
+
const nativeColorInput = colorPicker.shadowRoot?.querySelector('input[type="color"]');
|
|
603
|
+
|
|
604
|
+
if(nativeColorInput && !nativeColorInput._hasPageListener) {
|
|
605
|
+
nativeColorInput._hasPageListener = true;
|
|
606
|
+
|
|
607
|
+
const handleNativeChange = () => {
|
|
608
|
+
const propName = input.getAttribute('prop-name');
|
|
609
|
+
const newValue = nativeColorInput.value;
|
|
610
|
+
colorPicker.value = newValue;
|
|
611
|
+
input.value = newValue;
|
|
612
|
+
handlePropertyChange(propName, newValue);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// Listen to both input (while dragging) and change (on close)
|
|
616
|
+
nativeColorInput.addEventListener('input', handleNativeChange);
|
|
617
|
+
nativeColorInput.addEventListener('change', handleNativeChange);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Also listen to k-color-picker events for text input changes
|
|
621
|
+
if(colorPicker && !colorPicker._hasPageListener) {
|
|
622
|
+
colorPicker._hasPageListener = true;
|
|
623
|
+
const handleChange = () => {
|
|
624
|
+
const propName = input.getAttribute('prop-name');
|
|
625
|
+
const newValue = colorPicker.value;
|
|
626
|
+
input.value = newValue;
|
|
627
|
+
handlePropertyChange(propName, newValue);
|
|
628
|
+
};
|
|
629
|
+
colorPicker.addEventListener('change', handleChange);
|
|
630
|
+
colorPicker.addEventListener('input', handleChange);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// Run after initial render and periodically to catch dynamically added components
|
|
636
|
+
setTimeout(attachColorPickerListeners, 100);
|
|
637
|
+
setTimeout(attachColorPickerListeners, 500);
|
|
638
|
+
setTimeout(attachColorPickerListeners, 1000);
|
|
639
|
+
|
|
640
|
+
/*
|
|
641
|
+
Download Theme Button
|
|
642
|
+
*/
|
|
643
|
+
const downloadThemeBtn = document.getElementById('downloadTheme');
|
|
644
|
+
downloadThemeBtn.addEventListener('click', () => {
|
|
645
|
+
const css = getContext('customThemeCSS');
|
|
646
|
+
|
|
647
|
+
if(!css || css.trim() === ''){
|
|
648
|
+
alert('No theme changes to download. Make some changes first!');
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Create blob and download link
|
|
653
|
+
const blob = new Blob([css], { type: 'text/css' });
|
|
654
|
+
const url = URL.createObjectURL(blob);
|
|
655
|
+
const a = document.createElement('a');
|
|
656
|
+
a.href = url;
|
|
657
|
+
a.download = 'kempo-theme.css';
|
|
658
|
+
document.body.appendChild(a);
|
|
659
|
+
a.click();
|
|
660
|
+
document.body.removeChild(a);
|
|
661
|
+
URL.revokeObjectURL(url);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
/*
|
|
665
|
+
Upload Theme Button
|
|
666
|
+
*/
|
|
667
|
+
const uploadThemeBtn = document.getElementById('uploadThemeBtn');
|
|
668
|
+
uploadThemeBtn.addEventListener('click', () => {
|
|
669
|
+
const dialogContent = document.createElement('div');
|
|
670
|
+
dialogContent.className = 'p';
|
|
671
|
+
dialogContent.innerHTML = `
|
|
672
|
+
<p class="mb">Upload a previously downloaded theme CSS file to restore your theme settings.</p>
|
|
673
|
+
<input type="file" id="themeFileInput" accept=".css" class="mb" />
|
|
674
|
+
<div id="uploadPreview" class="mb" style="display: none;">
|
|
675
|
+
<label><strong>Preview:</strong></label>
|
|
676
|
+
<pre style="max-height: 200px; overflow: auto; background: var(--c_bg__alt); padding: var(--spacer_h); border-radius: var(--radius); font-size: 0.8rem;"><code id="uploadPreviewCode"></code></pre>
|
|
677
|
+
</div>
|
|
678
|
+
<p id="uploadError" class="tc-danger mb" style="display: none;"></p>
|
|
679
|
+
`;
|
|
680
|
+
|
|
681
|
+
let parsedTheme = null;
|
|
682
|
+
|
|
683
|
+
const dialog = window.KDialog.create(dialogContent, {
|
|
684
|
+
title: 'Upload Theme',
|
|
685
|
+
confirmText: 'Apply Theme',
|
|
686
|
+
confirmClasses: 'primary ml',
|
|
687
|
+
cancelText: 'Cancel',
|
|
688
|
+
width: '32rem',
|
|
689
|
+
confirmAction: (e) => {
|
|
690
|
+
if(!parsedTheme){
|
|
691
|
+
e.preventDefault();
|
|
692
|
+
const errorEl = dialogContent.querySelector('#uploadError');
|
|
693
|
+
errorEl.textContent = 'Please select a valid theme file first.';
|
|
694
|
+
errorEl.style.display = 'block';
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
applyUploadedTheme(parsedTheme);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Handle file selection
|
|
702
|
+
const fileInput = dialogContent.querySelector('#themeFileInput');
|
|
703
|
+
fileInput.addEventListener('change', (e) => {
|
|
704
|
+
const file = e.target.files[0];
|
|
705
|
+
if(!file) return;
|
|
706
|
+
|
|
707
|
+
const reader = new FileReader();
|
|
708
|
+
reader.onload = (evt) => {
|
|
709
|
+
const css = evt.target.result;
|
|
710
|
+
const previewEl = dialogContent.querySelector('#uploadPreview');
|
|
711
|
+
const previewCode = dialogContent.querySelector('#uploadPreviewCode');
|
|
712
|
+
const errorEl = dialogContent.querySelector('#uploadError');
|
|
713
|
+
|
|
714
|
+
try {
|
|
715
|
+
parsedTheme = parseThemeCSS(css);
|
|
716
|
+
if(Object.keys(parsedTheme).length === 0){
|
|
717
|
+
throw new Error('No valid CSS custom properties found in file.');
|
|
718
|
+
}
|
|
719
|
+
previewCode.textContent = css;
|
|
720
|
+
previewEl.style.display = 'block';
|
|
721
|
+
errorEl.style.display = 'none';
|
|
722
|
+
} catch(err) {
|
|
723
|
+
parsedTheme = null;
|
|
724
|
+
previewEl.style.display = 'none';
|
|
725
|
+
errorEl.textContent = err.message;
|
|
726
|
+
errorEl.style.display = 'block';
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
reader.readAsText(file);
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
/*
|
|
734
|
+
Parse uploaded CSS
|
|
735
|
+
*/
|
|
736
|
+
const parseThemeCSS = (css) => {
|
|
737
|
+
const result = {};
|
|
738
|
+
|
|
739
|
+
// Remove comments
|
|
740
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
741
|
+
|
|
742
|
+
// Match :root block
|
|
743
|
+
const rootMatch = css.match(/:root\s*\{([^}]+)\}/);
|
|
744
|
+
if(!rootMatch) throw new Error('No :root block found in CSS file.');
|
|
745
|
+
|
|
746
|
+
const declarations = rootMatch[1];
|
|
747
|
+
|
|
748
|
+
// Match CSS custom properties
|
|
749
|
+
const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g;
|
|
750
|
+
let match;
|
|
751
|
+
|
|
752
|
+
while((match = propRegex.exec(declarations)) !== null){
|
|
753
|
+
const [, propName, value] = match;
|
|
754
|
+
const trimmedValue = value.trim();
|
|
755
|
+
|
|
756
|
+
// Check if it's a light-dark() value - need to handle nested parentheses
|
|
757
|
+
if(trimmedValue.startsWith('light-dark(')){
|
|
758
|
+
// Find the comma that separates light and dark values
|
|
759
|
+
// by tracking parenthesis depth
|
|
760
|
+
const inner = trimmedValue.slice(11, -1); // Remove "light-dark(" and ")"
|
|
761
|
+
let depth = 0;
|
|
762
|
+
let splitIndex = -1;
|
|
763
|
+
|
|
764
|
+
for(let i = 0; i < inner.length; i++){
|
|
765
|
+
if(inner[i] === '(') depth++;
|
|
766
|
+
else if(inner[i] === ')') depth--;
|
|
767
|
+
else if(inner[i] === ',' && depth === 0){
|
|
768
|
+
splitIndex = i;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if(splitIndex !== -1){
|
|
774
|
+
result[propName] = {
|
|
775
|
+
light: inner.slice(0, splitIndex).trim(),
|
|
776
|
+
dark: inner.slice(splitIndex + 1).trim()
|
|
777
|
+
};
|
|
778
|
+
} else {
|
|
779
|
+
// Fallback if no comma found at depth 0
|
|
780
|
+
result[propName] = {
|
|
781
|
+
light: trimmedValue,
|
|
782
|
+
dark: trimmedValue
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
// Single value - apply to both themes
|
|
787
|
+
result[propName] = {
|
|
788
|
+
light: trimmedValue,
|
|
789
|
+
dark: trimmedValue
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return result;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
/*
|
|
798
|
+
Apply uploaded theme
|
|
799
|
+
*/
|
|
800
|
+
const applyUploadedTheme = (parsedTheme) => {
|
|
801
|
+
// Reset currentTheme to defaults first
|
|
802
|
+
Object.keys(defaultTheme).forEach(prop => {
|
|
803
|
+
const defaultValue = defaultTheme[prop];
|
|
804
|
+
if(typeof defaultValue === 'object'){
|
|
805
|
+
currentTheme.light[prop] = defaultValue.light;
|
|
806
|
+
currentTheme.dark[prop] = defaultValue.dark;
|
|
807
|
+
} else {
|
|
808
|
+
currentTheme.light[prop] = defaultValue;
|
|
809
|
+
currentTheme.dark[prop] = defaultValue;
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Only apply values that differ from defaults
|
|
814
|
+
Object.entries(parsedTheme).forEach(([prop, value]) => {
|
|
815
|
+
if(currentTheme.light[prop] !== undefined){
|
|
816
|
+
const defaultValue = defaultTheme[prop];
|
|
817
|
+
const defaultLight = typeof defaultValue === 'object' ? defaultValue.light : defaultValue;
|
|
818
|
+
const defaultDark = typeof defaultValue === 'object' ? defaultValue.dark : defaultValue;
|
|
819
|
+
|
|
820
|
+
// Only set if different from default
|
|
821
|
+
if(value.light !== defaultLight){
|
|
822
|
+
currentTheme.light[prop] = value.light;
|
|
823
|
+
}
|
|
824
|
+
if(value.dark !== defaultDark){
|
|
825
|
+
currentTheme.dark[prop] = value.dark;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// Update all input values
|
|
831
|
+
updateInputValues();
|
|
832
|
+
|
|
833
|
+
// Regenerate and apply CSS
|
|
834
|
+
const diff = getThemeDiff();
|
|
835
|
+
const css = generateThemeCSS(diff);
|
|
836
|
+
applyThemeCSS(css);
|
|
837
|
+
|
|
838
|
+
console.log('Uploaded theme applied, diff:', diff);
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// Make Dialog available after component loads
|
|
842
|
+
customElements.whenDefined('k-dialog').then(() => {
|
|
843
|
+
window.KDialog = customElements.get('k-dialog');
|
|
844
|
+
});
|
|
845
|
+
</script>
|
|
846
|
+
</body>
|
|
847
|
+
</html>
|