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.
Files changed (42) hide show
  1. package/.vscode/settings.json +2 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/kempo.min.css +1 -1
  4. package/docs/borders-spacing.html +446 -0
  5. package/docs/buttons.html +262 -0
  6. package/docs/colors.html +199 -0
  7. package/docs/components/ThemePropertyInput.js +2 -2
  8. package/docs/elevation.html +509 -0
  9. package/docs/examples/responsive-grid.html +1 -1
  10. package/docs/index.html +237 -1477
  11. package/docs/inputs.html +252 -0
  12. package/docs/kempo.css +13 -0
  13. package/docs/kempo.min.css +1 -1
  14. package/docs/layout.html +311 -0
  15. package/docs/tables.html +262 -0
  16. package/docs/theme-editor.html +936 -802
  17. package/docs/typography.html +275 -0
  18. package/docs/utilities.html +172 -0
  19. package/docs-src/.config.js +26 -0
  20. package/docs-src/borders-spacing.page.html +297 -0
  21. package/docs-src/buttons.page.html +113 -0
  22. package/docs-src/colors.page.html +50 -0
  23. package/docs-src/default.template.html +22 -0
  24. package/docs-src/elevation.page.html +361 -0
  25. package/docs-src/examples/responsive-grid.html +33 -0
  26. package/docs-src/head.fragment.html +16 -0
  27. package/docs-src/index.page.html +95 -0
  28. package/docs-src/inputs.page.html +103 -0
  29. package/docs-src/layout.page.html +163 -0
  30. package/docs-src/nav.fragment.html +116 -0
  31. package/docs-src/tables.page.html +114 -0
  32. package/docs-src/theme-editor.page.html +850 -0
  33. package/docs-src/theme-editor.template.html +11 -0
  34. package/docs-src/typography.page.html +126 -0
  35. package/docs-src/utilities.page.html +26 -0
  36. package/llms.txt +2 -0
  37. package/package.json +3 -2
  38. package/scripts/build.js +7 -1
  39. package/src/kempo.css +13 -0
  40. package/tests/typography.browser-test.js +26 -0
  41. package/docs/init.js +0 -4
  42. package/docs/nav.js +0 -33
@@ -1,677 +1,809 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Kempo CSS</title>
7
- <link rel="icon" type="image/png" sizes="48x48" href="./media/icon48.png">
8
- <link rel="manifest" href="./manifest.json" />
9
- <link rel="stylesheet" href="./kempo.min.css" />
10
- <link rel="stylesheet" href="./kempo-hljs.min.css" />
11
- <script src="./init.js"></script>
12
- <style>
13
- html {
14
- scrollbar-gutter: unset;
15
- }
16
-
17
- body {
18
- margin: 0;
19
- padding: 0;
20
- overflow: hidden;
21
- height: 100vh;
22
- }
23
-
24
- #grid-container {
25
- display: grid;
26
- grid-template-rows: auto 1fr;
27
- height: 100vh;
28
- }
29
-
30
- k-split {
31
- height: 100%;
32
- }
33
- </style>
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
- <div id="grid-container">
37
- <nav class="d-f bg-primary">
38
- <button id="toggleNavSideMenu" class="link">
39
- <k-icon name="menu"></k-icon>
40
- </button>
41
- <a href="./" class="d-if ph" style="align-items: center">
42
- <img src="./media/icon32.png" alt="Kempo CSS Icon" class="pr" />
43
- Kempo CSS
44
- </a>
45
- <a href="./theme-editor.html">Theme Editor</a>
46
- <div class="flex"></div>
47
- <a href="https://github.com/dustinpoissant/kempo-css?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
48
- target="_blank"><k-icon name="license"></k-icon></a>
49
- <a href="https://github.com/dustinpoissant/kempo-css" target="_blank"><k-icon name="github-mark"></k-icon></a>
50
- <k-theme-switcher></k-theme-switcher>
51
- </nav>
52
- <k-side-menu id="navSideMenu">
53
- <menu>
54
- <div class="ta-center bb mb r0">
55
- <a href="./"><h1 class="tc-primary">Kempo CSS</h1></a>
56
- <h3 class="tc-primary">Theme Editor</h3>
57
- <a href="./"><img src="./media/icon128.png" alt="Kempo UI Icon" /></a>
58
- </div>
59
- <h5>Sections</h5>
60
- <a href="#theme-colors">Theme Colors</a><br />
61
- <a href="#background-neutral-colors">Background & Neutral Colors</a><br />
62
- <a href="#button-styles">Button Styles</a><br />
63
- <a href="#input-styles">Input Styles</a><br />
64
- <a href="#focus-effects">Focus & Effects</a><br />
65
- <a href="#elevation-shadows">Elevation & Shadows</a><br />
66
- <a href="#typography">Typography</a><br />
67
- <a href="#spacing-layout">Spacing & Layout</a><br />
68
- <a href="#effects-animation">Effects & Animation</a><br />
69
- <hr />
70
- <a href="https://github.com/dustinpoissant/kempo-css?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
71
- target="_blank"><k-icon name="license"></k-icon> Read the Lisence</a><br />
72
- <a href="https://github.com/dustinpoissant/kempo-css" target="_blank"><k-icon name="github-mark"></k-icon> View
73
- on
74
- GitHub</a>
75
- <div style="height:33vh"></div>
76
- </menu>
77
- </k-side-menu>
78
- <k-split style="--left_width: 24rem;">
79
- <aside class="d-b p" style="overflow-y: auto; height: 100%;">
80
- <div class="mb">
81
- <div class="d-f full btn-grp mb">
82
- <button id="downloadTheme" class="primary flex">Download Theme</button>
83
- <button id="uploadThemeBtn" class="secondary flex">Upload Theme</button>
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 &amp; 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
- <label class="d-b mb-q">
86
- <strong>Editing Theme</strong>
87
- <select id="editingThemeSelect" class="w-100">
88
- <option value="light">Light</option>
89
- <option value="dark">Dark</option>
90
- </select>
91
- </label>
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
- <hr class="mb">
94
- <div id="themeInputs"></div>
95
- </aside>
96
- <div slot="right" style="overflow-y: auto; height: 100%;">
97
- <main style="padding: var(--spacer);">
98
- <k-import src="./demo.inc.html"></k-import>
99
- </main>
100
- </div>
101
- </k-split>
102
- </div>
103
- <script src="./nav.js" type="module"></script>
104
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/SideMenu.js"></script>
105
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Split.js"></script>
106
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Icon.js"></script>
107
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/ThemeSwitcher.js"></script>
108
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Import.js"></script>
109
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Resize.js"></script>
110
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Card.js"></script>
111
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/ColorPicker.js"></script>
112
- <script type="module" src="https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/dist/components/Dialog.js"></script>
113
- <script src="./components/ThemePropertyInput.js" type="module"></script>
114
- <script type="module">
115
- import theme from 'https://cdn.jsdelivr.net/npm/kempo-ui@0.1.6/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);
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
- 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);
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
- 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
- }
582
+ });
583
+
584
+ themeInputsContainer.appendChild(section);
450
585
  });
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);
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
- 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;
612
+
613
+ theme.subscribe(() => {
614
+ editingTheme.value = theme.getCalculated();
615
+ editingThemeSelect.value = editingTheme.value;
616
+ updateInputValues();
472
617
  });
473
- };
474
618
 
475
- editingThemeSelect.addEventListener('change', () => {
476
- editingTheme.value = editingThemeSelect.value;
477
- theme.set(editingTheme.value);
478
- updateInputValues();
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
- 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;
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
- 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};`);
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
- 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);
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
- 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);
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
- // 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 = `
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
- 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.');
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
- 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';
832
+ applyUploadedTheme(parsedTheme);
730
833
  }
731
- };
732
- reader.readAsText(file);
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
- 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;
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
- // Fallback if no comma found at depth 0
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
- // Only apply values that differ from defaults
817
- Object.entries(parsedTheme).forEach(([prop, value]) => {
818
- if(currentTheme.light[prop] !== undefined){
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
- 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;
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
- if(value.dark !== defaultDark){
828
- currentTheme.dark[prop] = value.dark;
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
- // 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>
980
+ </script>
981
+
982
+
849
983
  </body>
850
- </html>
984
+ </html>