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