kempo-css 2.1.3 → 2.1.4

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