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
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
<page
|
|
2
|
+
template="theme-editor"
|
|
3
|
+
title="Theme Editor - Kempo CSS"
|
|
4
|
+
>
|
|
5
|
+
<content location="styles">
|
|
6
|
+
<style>
|
|
7
|
+
html {
|
|
8
|
+
scrollbar-gutter: unset;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#grid-container {
|
|
19
|
+
height: calc(100vh - 4rem);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
k-split {
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
</content>
|
|
27
|
+
<content>
|
|
28
|
+
<div id="grid-container">
|
|
29
|
+
<k-split style="--pane_1_size: 24rem;">
|
|
30
|
+
<aside
|
|
31
|
+
class="d-b p"
|
|
32
|
+
style="overflow-y: auto; height: 100%;"
|
|
33
|
+
>
|
|
34
|
+
<div class="mb">
|
|
35
|
+
<div class="d-f full btn-grp mb">
|
|
36
|
+
<button
|
|
37
|
+
id="downloadTheme"
|
|
38
|
+
class="primary flex"
|
|
39
|
+
>Download Theme</button>
|
|
40
|
+
<button
|
|
41
|
+
id="uploadThemeBtn"
|
|
42
|
+
class="secondary flex"
|
|
43
|
+
>Upload Theme</button>
|
|
44
|
+
</div>
|
|
45
|
+
<label class="d-b mb-q">
|
|
46
|
+
<strong>Editing Theme</strong>
|
|
47
|
+
<select
|
|
48
|
+
id="editingThemeSelect"
|
|
49
|
+
class="w-100"
|
|
50
|
+
>
|
|
51
|
+
<option value="light">Light</option>
|
|
52
|
+
<option value="dark">Dark</option>
|
|
53
|
+
</select>
|
|
54
|
+
</label>
|
|
55
|
+
</div>
|
|
56
|
+
<hr class="mb">
|
|
57
|
+
<div id="themeInputs"></div>
|
|
58
|
+
</aside>
|
|
59
|
+
<div
|
|
60
|
+
slot="right"
|
|
61
|
+
style="overflow-y: auto; height: 100%;"
|
|
62
|
+
>
|
|
63
|
+
<main style="padding: var(--spacer);">
|
|
64
|
+
<k-import src="./demo.inc.html"></k-import>
|
|
65
|
+
</main>
|
|
66
|
+
</div>
|
|
67
|
+
</k-split>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<script
|
|
71
|
+
type="module"
|
|
72
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Nav.js"
|
|
73
|
+
></script>
|
|
74
|
+
<script
|
|
75
|
+
type="module"
|
|
76
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Aside.js"
|
|
77
|
+
></script>
|
|
78
|
+
<script
|
|
79
|
+
type="module"
|
|
80
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Split.js"
|
|
81
|
+
></script>
|
|
82
|
+
<script
|
|
83
|
+
type="module"
|
|
84
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Icon.js"
|
|
85
|
+
></script>
|
|
86
|
+
<script
|
|
87
|
+
type="module"
|
|
88
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/ThemeSwitcher.js"
|
|
89
|
+
></script>
|
|
90
|
+
<script
|
|
91
|
+
type="module"
|
|
92
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Import.js"
|
|
93
|
+
></script>
|
|
94
|
+
<script
|
|
95
|
+
type="module"
|
|
96
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Resize.js"
|
|
97
|
+
></script>
|
|
98
|
+
<script
|
|
99
|
+
type="module"
|
|
100
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Card.js"
|
|
101
|
+
></script>
|
|
102
|
+
<script
|
|
103
|
+
type="module"
|
|
104
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/ColorPicker.js"
|
|
105
|
+
></script>
|
|
106
|
+
<script
|
|
107
|
+
type="module"
|
|
108
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/components/Dialog.js"
|
|
109
|
+
></script>
|
|
110
|
+
<script
|
|
111
|
+
src="./components/ThemePropertyInput.js"
|
|
112
|
+
type="module"
|
|
113
|
+
></script>
|
|
114
|
+
<script type="module">
|
|
115
|
+
import theme from 'https://cdn.jsdelivr.net/npm/kempo-ui@0.3/dist/utils/theme.js';
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
Simple Context for storing theme CSS
|
|
119
|
+
*/
|
|
120
|
+
const themeContext = {};
|
|
121
|
+
const setContext = (key, value) => { themeContext[key] = value; };
|
|
122
|
+
const getContext = key => themeContext[key];
|
|
123
|
+
|
|
124
|
+
const defaultTheme = {
|
|
125
|
+
"--ff_body": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
|
|
126
|
+
"--ff_heading": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
|
|
127
|
+
"--ff_mono": "Consolas, monaco, monospace",
|
|
128
|
+
"--fs_base": "16px",
|
|
129
|
+
"--fs_small": "calc(0.6 * var(--fs_base))",
|
|
130
|
+
"--fs_large": "calc(1.5 * var(--fs_base))",
|
|
131
|
+
"--fs_h6": "var(--fs_base)",
|
|
132
|
+
"--fs_h5": "calc(1.25 * var(--fs_base))",
|
|
133
|
+
"--fs_h4": "calc(1.5 * var(--fs_base))",
|
|
134
|
+
"--fs_h3": "calc(1.75 * var(--fs_base))",
|
|
135
|
+
"--fs_h2": "calc(2 * var(--fs_base))",
|
|
136
|
+
"--fs_h1": "calc(2.5 * var(--fs_base))",
|
|
137
|
+
"--fw_base": "400",
|
|
138
|
+
"--fw_bold": "700",
|
|
139
|
+
"--spacer": "1rem",
|
|
140
|
+
"--spacer_h": "calc(0.5 * var(--spacer))",
|
|
141
|
+
"--spacer_q": "calc(0.25 * var(--spacer))",
|
|
142
|
+
"--line-height": "1.35em",
|
|
143
|
+
"--container_width": "90rem",
|
|
144
|
+
"--animation_ms": "256ms",
|
|
145
|
+
"--radius": "0.25rem",
|
|
146
|
+
"--link_decoration": "underline",
|
|
147
|
+
"--input_padding": "var(--spacer_h) var(--spacer)",
|
|
148
|
+
"--input_border_width": "1px",
|
|
149
|
+
"--btn_padding": "var(--spacer_h) var(--spacer)",
|
|
150
|
+
"--c_bg": { light: "rgb(249, 249, 249)", dark: "rgb(51, 51, 51)" },
|
|
151
|
+
"--c_bg__inv": { light: "rgb(51, 51, 51)", dark: "rgb(249, 249, 249)" },
|
|
152
|
+
"--c_bg__alt": { light: "rgb(238, 238, 238)", dark: "rgb(34, 34, 34)" },
|
|
153
|
+
"--c_overscroll": { light: "rgb(255, 255, 255)", dark: "rgb(0, 0, 0)" },
|
|
154
|
+
"--c_border": { light: "rgb(204, 204, 204)", dark: "rgb(119, 119, 119)" },
|
|
155
|
+
"--c_border__inv": { light: "var(--d_c_bg_border)", dark: "rgb(204, 204, 204)" },
|
|
156
|
+
"--c_primary": "rgb(51, 102, 255)",
|
|
157
|
+
"--c_primary__hover": "rgb(17, 68, 221)",
|
|
158
|
+
"--c_secondary": "rgb(153, 51, 255)",
|
|
159
|
+
"--c_secondary__hover": "rgb(119, 17, 221)",
|
|
160
|
+
"--c_success": "rgb(0, 136, 0)",
|
|
161
|
+
"--c_success__hover": "rgb(0, 102, 0)",
|
|
162
|
+
"--c_warning": "rgb(255, 102, 0)",
|
|
163
|
+
"--c_warning__hover": "rgb(221, 68, 0)",
|
|
164
|
+
"--c_danger": "rgb(255, 0, 51)",
|
|
165
|
+
"--c_danger__hover": "rgb(221, 0, 17)",
|
|
166
|
+
"--c_input_accent": "rgb(51, 102, 255)",
|
|
167
|
+
"--c_input_border": "var(--c_border)",
|
|
168
|
+
"--c_highlight": { light: "rgba(41, 100, 210, 0.25)", dark: "rgba(0, 89, 255, 0.25)" },
|
|
169
|
+
"--tc": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
170
|
+
"--tc_dark": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
171
|
+
"--tc_light": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
172
|
+
"--tc_inv": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
173
|
+
"--tc_muted": { light: "rgba(0, 0, 0, 0.5)", dark: "rgba(255, 255, 255, 0.5)" },
|
|
174
|
+
"--tc_on_primary": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
175
|
+
"--tc_on_secondary": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
176
|
+
"--tc_on_success": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
177
|
+
"--tc_on_warning": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
178
|
+
"--tc_on_danger": { light: "rgba(255, 255, 255, 0.93)", dark: "rgba(255, 255, 255, 0.93)" },
|
|
179
|
+
"--c_overlay": "rgba(0, 0, 0, 0.5)",
|
|
180
|
+
"--tc_primary": { light: "#36f", dark: "rgb(138, 180, 248)" },
|
|
181
|
+
"--tc_secondary": { light: "#93f", dark: "rgb(187, 102, 255)" },
|
|
182
|
+
"--tc_success": { light: "#080", dark: "rgb(102, 187, 102)" },
|
|
183
|
+
"--tc_warning": { light: "#f60", dark: "rgb(255, 153, 51)" },
|
|
184
|
+
"--tc_danger": { light: "#f03", dark: "rgb(255, 85, 119)" },
|
|
185
|
+
"--btn_box_shadow": "0 0 0 transparent",
|
|
186
|
+
"--btn_box_shadow__hover": "0 0 0 transparent",
|
|
187
|
+
"--btn_border": "transparent",
|
|
188
|
+
"--btn_bg": { light: "rgb(221, 221, 221)", dark: "rgb(170, 170, 170)" },
|
|
189
|
+
"--btn_bg__hover": { light: "rgb(204, 204, 204)", dark: "rgb(187, 187, 187)" },
|
|
190
|
+
"--btn_tc": { light: "rgba(0, 0, 0, 0.93)", dark: "rgba(0, 0, 0, 0.93)" },
|
|
191
|
+
"--btn_transparent__hover": { light: "rgba(0, 0, 0, 0.05)", dark: "rgba(255, 255, 255, 0.05)" },
|
|
192
|
+
"--tc_link": "var(--tc_primary)",
|
|
193
|
+
"--tc_link__hover": "var(--tc_secondary)",
|
|
194
|
+
"--tc_link__inv": "var(--tc_primary__inv)",
|
|
195
|
+
"--tc_link__inv__hover": "var(--tc_secondary__inv)",
|
|
196
|
+
"--focus_shadow": "0 0 2px 2px var(--c_primary)",
|
|
197
|
+
"--focus_shadow_on_primary": "0 0 2px 2px var(--tc_on_primary)",
|
|
198
|
+
"--input_bg": { light: "white", dark: "var(--c_bg__alt)" },
|
|
199
|
+
"--input_tc": { light: "rgba(0, 0, 0, 0.93)", dark: "var(--tc)" },
|
|
200
|
+
"--shadow_color": "black",
|
|
201
|
+
"--shadow_size": "1px",
|
|
202
|
+
"--shadow_base_opacity": "0.08",
|
|
203
|
+
"--shadow_opacity_step": "0.01",
|
|
204
|
+
"--date_picker_icon_filter": { light: "invert(0)", dark: "invert(1)" }
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
Current Theme State
|
|
209
|
+
*/
|
|
210
|
+
const currentTheme = { light: {}, dark: {} };
|
|
211
|
+
const editingTheme = { value: theme.getCalculated() };
|
|
212
|
+
const availableProperties = Object.keys(defaultTheme);
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
Initialize Theme State
|
|
216
|
+
*/
|
|
217
|
+
Object.entries(defaultTheme).forEach(([prop, value]) => {
|
|
218
|
+
if (typeof value === 'object' && value.light) {
|
|
219
|
+
currentTheme.light[prop] = value.light;
|
|
220
|
+
currentTheme.dark[prop] = value.dark;
|
|
221
|
+
} else {
|
|
222
|
+
currentTheme.light[prop] = value;
|
|
223
|
+
currentTheme.dark[prop] = value;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
Render Theme Inputs
|
|
229
|
+
*/
|
|
230
|
+
const themeInputsContainer = document.getElementById('themeInputs');
|
|
231
|
+
|
|
232
|
+
const propertyCategories = [
|
|
233
|
+
{
|
|
234
|
+
title: 'Theme Colors',
|
|
235
|
+
props: [
|
|
236
|
+
'--c_primary',
|
|
237
|
+
'--c_secondary',
|
|
238
|
+
'--c_success',
|
|
239
|
+
'--c_warning',
|
|
240
|
+
'--c_danger'
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
title: 'Background & Neutral Colors',
|
|
245
|
+
props: [
|
|
246
|
+
'--c_bg', '--c_overscroll', '--c_border', '--c_overlay'
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
title: 'Button Styles',
|
|
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);
|
|
370
|
+
});
|
|
371
|
+
return words.join(' ');
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const titleToId = title => title.toLowerCase().replace(/\s*&\s*/g, '-').replace(/\s+/g, '-');
|
|
375
|
+
|
|
376
|
+
propertyCategories.forEach(({ title, props }) => {
|
|
377
|
+
const section = document.createElement('div');
|
|
378
|
+
section.className = 'mb';
|
|
379
|
+
section.id = titleToId(title);
|
|
380
|
+
|
|
381
|
+
const heading = document.createElement('h4');
|
|
382
|
+
heading.className = 'mb-h';
|
|
383
|
+
heading.textContent = title;
|
|
384
|
+
section.appendChild(heading);
|
|
385
|
+
|
|
386
|
+
props.forEach(prop => {
|
|
387
|
+
if (defaultTheme[prop] === undefined) return;
|
|
388
|
+
|
|
389
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
390
|
+
|
|
391
|
+
if (isColorProperty(prop)) {
|
|
392
|
+
const input = document.createElement('k-theme-property-input');
|
|
393
|
+
input.setAttribute('prop-name', prop);
|
|
394
|
+
input.setAttribute('label', propToLabel(prop));
|
|
395
|
+
input.setAttribute('available-properties', JSON.stringify(availableProperties));
|
|
396
|
+
input.setAttribute('value', value);
|
|
397
|
+
|
|
398
|
+
if (isColorValue(value)) {
|
|
399
|
+
input.setAttribute('mode', 'color');
|
|
400
|
+
} else {
|
|
401
|
+
input.setAttribute('mode', 'var');
|
|
402
|
+
if (value.startsWith('var(')) {
|
|
403
|
+
const computedColor = getComputedValue(value);
|
|
404
|
+
if (computedColor && computedColor !== 'rgba(0, 0, 0, 0)' && !computedColor.includes('NaN')) {
|
|
405
|
+
input.setAttribute('initial-color', computedColor);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
input.className = 'mb';
|
|
411
|
+
section.appendChild(input);
|
|
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
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
themeInputsContainer.appendChild(section);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
/*
|
|
456
|
+
Handle Theme Switch
|
|
457
|
+
*/
|
|
458
|
+
const editingThemeSelect = document.getElementById('editingThemeSelect');
|
|
459
|
+
editingThemeSelect.value = editingTheme.value;
|
|
460
|
+
|
|
461
|
+
const updateInputValues = () => {
|
|
462
|
+
document.querySelectorAll('k-theme-property-input').forEach(input => {
|
|
463
|
+
const prop = input.getAttribute('prop-name');
|
|
464
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
465
|
+
input.setAttribute('value', value);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
document.querySelectorAll('input[data-prop-name]').forEach(input => {
|
|
469
|
+
const prop = input.getAttribute('data-prop-name');
|
|
470
|
+
const value = currentTheme[editingTheme.value][prop];
|
|
471
|
+
input.value = value;
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
editingThemeSelect.addEventListener('change', () => {
|
|
476
|
+
editingTheme.value = editingThemeSelect.value;
|
|
477
|
+
theme.set(editingTheme.value);
|
|
478
|
+
updateInputValues();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
theme.subscribe(() => {
|
|
482
|
+
editingTheme.value = theme.getCalculated();
|
|
483
|
+
editingThemeSelect.value = editingTheme.value;
|
|
484
|
+
updateInputValues();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
/*
|
|
488
|
+
Change Tracking
|
|
489
|
+
*/
|
|
490
|
+
const getThemeDiff = () => {
|
|
491
|
+
const diff = {};
|
|
492
|
+
|
|
493
|
+
['light', 'dark'].forEach(themeMode => {
|
|
494
|
+
Object.entries(currentTheme[themeMode]).forEach(([prop, value]) => {
|
|
495
|
+
const defaultValue = typeof defaultTheme[prop] === 'object'
|
|
496
|
+
? defaultTheme[prop][themeMode]
|
|
497
|
+
: defaultTheme[prop];
|
|
498
|
+
|
|
499
|
+
const isEqual = isColorProperty(prop)
|
|
500
|
+
? colorsEqual(value, defaultValue)
|
|
501
|
+
: value === defaultValue;
|
|
502
|
+
|
|
503
|
+
if (!isEqual) {
|
|
504
|
+
if (!diff[prop]) diff[prop] = {};
|
|
505
|
+
diff[prop][themeMode] = value;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return diff;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const handlePropertyChange = (prop, value) => {
|
|
514
|
+
currentTheme[editingTheme.value][prop] = value;
|
|
515
|
+
|
|
516
|
+
const diff = getThemeDiff();
|
|
517
|
+
const css = generateThemeCSS(diff);
|
|
518
|
+
applyThemeCSS(css);
|
|
519
|
+
console.log('Theme CSS:', css);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/*
|
|
523
|
+
CSS Generation
|
|
524
|
+
*/
|
|
525
|
+
const generateThemeCSS = (diff) => {
|
|
526
|
+
if (Object.keys(diff).length === 0) return '';
|
|
527
|
+
|
|
528
|
+
const cssLines = [];
|
|
529
|
+
|
|
530
|
+
Object.entries(diff).forEach(([prop, value]) => {
|
|
531
|
+
if (typeof value === 'object') {
|
|
532
|
+
// Property has light/dark variants
|
|
533
|
+
const hasLight = value.light !== undefined;
|
|
534
|
+
const hasDark = value.dark !== undefined;
|
|
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};`);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
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
|
+
|
|
587
|
+
document.querySelectorAll('input[data-prop-name]').forEach(input => {
|
|
588
|
+
input.addEventListener('input', (e) => {
|
|
589
|
+
const prop = e.target.getAttribute('data-prop-name');
|
|
590
|
+
const value = e.target.value;
|
|
591
|
+
handlePropertyChange(prop, value);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
/*
|
|
596
|
+
Setup color picker change listeners after components render
|
|
597
|
+
Listen directly to native color inputs since k-color-picker doesn't always re-dispatch events
|
|
598
|
+
*/
|
|
599
|
+
const attachColorPickerListeners = () => {
|
|
600
|
+
document.querySelectorAll('k-theme-property-input').forEach(input => {
|
|
601
|
+
const colorPicker = input.shadowRoot?.querySelector('k-color-picker');
|
|
602
|
+
if (!colorPicker) return;
|
|
603
|
+
|
|
604
|
+
// Get the native color input inside k-color-picker
|
|
605
|
+
const nativeColorInput = colorPicker.shadowRoot?.querySelector('input[type="color"]');
|
|
606
|
+
|
|
607
|
+
if (nativeColorInput && !nativeColorInput._hasPageListener) {
|
|
608
|
+
nativeColorInput._hasPageListener = true;
|
|
609
|
+
|
|
610
|
+
const handleNativeChange = () => {
|
|
611
|
+
const propName = input.getAttribute('prop-name');
|
|
612
|
+
const newValue = nativeColorInput.value;
|
|
613
|
+
colorPicker.value = newValue;
|
|
614
|
+
input.value = newValue;
|
|
615
|
+
handlePropertyChange(propName, newValue);
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// Listen to both input (while dragging) and change (on close)
|
|
619
|
+
nativeColorInput.addEventListener('input', handleNativeChange);
|
|
620
|
+
nativeColorInput.addEventListener('change', handleNativeChange);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Also listen to k-color-picker events for text input changes
|
|
624
|
+
if (colorPicker && !colorPicker._hasPageListener) {
|
|
625
|
+
colorPicker._hasPageListener = true;
|
|
626
|
+
const handleChange = () => {
|
|
627
|
+
const propName = input.getAttribute('prop-name');
|
|
628
|
+
const newValue = colorPicker.value;
|
|
629
|
+
input.value = newValue;
|
|
630
|
+
handlePropertyChange(propName, newValue);
|
|
631
|
+
};
|
|
632
|
+
colorPicker.addEventListener('change', handleChange);
|
|
633
|
+
colorPicker.addEventListener('input', handleChange);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// Run after initial render and periodically to catch dynamically added components
|
|
639
|
+
setTimeout(attachColorPickerListeners, 100);
|
|
640
|
+
setTimeout(attachColorPickerListeners, 500);
|
|
641
|
+
setTimeout(attachColorPickerListeners, 1000);
|
|
642
|
+
|
|
643
|
+
/*
|
|
644
|
+
Download Theme Button
|
|
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 = `
|
|
675
|
+
<p class="mb">Upload a previously downloaded theme CSS file to restore your theme settings.</p>
|
|
676
|
+
<input type="file" id="themeFileInput" accept=".css" class="mb" />
|
|
677
|
+
<div id="uploadPreview" class="mb" style="display: none;">
|
|
678
|
+
<label><strong>Preview:</strong></label>
|
|
679
|
+
<pre style="max-height: 200px; overflow: auto; background: var(--c_bg__alt); padding: var(--spacer_h); border-radius: var(--radius); font-size: 0.8rem;"><code id="uploadPreviewCode"></code></pre>
|
|
680
|
+
</div>
|
|
681
|
+
<p id="uploadError" class="tc-danger mb" style="display: none;"></p>
|
|
682
|
+
`;
|
|
683
|
+
|
|
684
|
+
let parsedTheme = null;
|
|
685
|
+
|
|
686
|
+
const dialog = window.KDialog.create(dialogContent, {
|
|
687
|
+
title: 'Upload Theme',
|
|
688
|
+
confirmText: 'Apply Theme',
|
|
689
|
+
confirmClasses: 'primary ml',
|
|
690
|
+
cancelText: 'Cancel',
|
|
691
|
+
width: '32rem',
|
|
692
|
+
confirmAction: (e) => {
|
|
693
|
+
if (!parsedTheme) {
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
const errorEl = dialogContent.querySelector('#uploadError');
|
|
696
|
+
errorEl.textContent = 'Please select a valid theme file first.';
|
|
697
|
+
errorEl.style.display = 'block';
|
|
698
|
+
return;
|
|
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.');
|
|
721
|
+
}
|
|
722
|
+
previewCode.textContent = css;
|
|
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';
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
reader.readAsText(file);
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
/*
|
|
737
|
+
Parse uploaded CSS
|
|
738
|
+
*/
|
|
739
|
+
const parseThemeCSS = (css) => {
|
|
740
|
+
const result = {};
|
|
741
|
+
|
|
742
|
+
// Remove comments
|
|
743
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
744
|
+
|
|
745
|
+
// Match :root block
|
|
746
|
+
const rootMatch = css.match(/:root\s*\{([^}]+)\}/);
|
|
747
|
+
if (!rootMatch) throw new Error('No :root block found in CSS file.');
|
|
748
|
+
|
|
749
|
+
const declarations = rootMatch[1];
|
|
750
|
+
|
|
751
|
+
// Match CSS custom properties
|
|
752
|
+
const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g;
|
|
753
|
+
let match;
|
|
754
|
+
|
|
755
|
+
while ((match = propRegex.exec(declarations)) !== null) {
|
|
756
|
+
const [, propName, value] = match;
|
|
757
|
+
const trimmedValue = value.trim();
|
|
758
|
+
|
|
759
|
+
// Check if it's a light-dark() value - need to handle nested parentheses
|
|
760
|
+
if (trimmedValue.startsWith('light-dark(')) {
|
|
761
|
+
// Find the comma that separates light and dark values
|
|
762
|
+
// by tracking parenthesis depth
|
|
763
|
+
const inner = trimmedValue.slice(11, -1); // Remove "light-dark(" and ")"
|
|
764
|
+
let depth = 0;
|
|
765
|
+
let splitIndex = -1;
|
|
766
|
+
|
|
767
|
+
for (let i = 0; i < inner.length; i++) {
|
|
768
|
+
if (inner[i] === '(') depth++;
|
|
769
|
+
else if (inner[i] === ')') depth--;
|
|
770
|
+
else if (inner[i] === ',' && depth === 0) {
|
|
771
|
+
splitIndex = i;
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
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
|
+
} else {
|
|
782
|
+
// Fallback if no comma found at depth 0
|
|
783
|
+
result[propName] = {
|
|
784
|
+
light: trimmedValue,
|
|
785
|
+
dark: trimmedValue
|
|
786
|
+
};
|
|
787
|
+
}
|
|
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
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// Only apply values that differ from defaults
|
|
817
|
+
Object.entries(parsedTheme).forEach(([prop, value]) => {
|
|
818
|
+
if (currentTheme.light[prop] !== undefined) {
|
|
819
|
+
const defaultValue = defaultTheme[prop];
|
|
820
|
+
const defaultLight = typeof defaultValue === 'object' ? defaultValue.light : defaultValue;
|
|
821
|
+
const defaultDark = typeof defaultValue === 'object' ? defaultValue.dark : defaultValue;
|
|
822
|
+
|
|
823
|
+
// Only set if different from default
|
|
824
|
+
if (value.light !== defaultLight) {
|
|
825
|
+
currentTheme.light[prop] = value.light;
|
|
826
|
+
}
|
|
827
|
+
if (value.dark !== defaultDark) {
|
|
828
|
+
currentTheme.dark[prop] = value.dark;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Update all input values
|
|
834
|
+
updateInputValues();
|
|
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>
|
|
849
|
+
</content>
|
|
850
|
+
</page>
|