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