kempo-ui 0.1.3 → 0.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.
@@ -0,0 +1,8 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import theme from"../utils/theme.js";export default class ThemeSelect extends ShadowComponent{static properties={currentTheme:{type:String,reflect:!0,attribute:"current-theme"}};constructor(){super(),this.currentTheme=theme.get()}handleChange=e=>{theme.set(e.target.value)};connectedCallback(){super.connectedCallback(),this.unsubscribe=theme.subscribe(e=>{this.currentTheme=e})}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribe&&this.unsubscribe()}render(){const e=this.childNodes.length>0;return html`
2
+ ${e?html`<label><slot></slot></label>`:""}
3
+ <select @change=${this.handleChange} .value=${this.currentTheme}>
4
+ <option value="light">Light</option>
5
+ <option value="dark">Dark</option>
6
+ <option value="auto">System Default</option>
7
+ </select>
8
+ `}static setTheme(e){theme.set(e)}static getCurrentTheme(){return theme.get()}static getCalculatedCurrentTheme(){return theme.getCalculated()}}customElements.define("k-theme-select",ThemeSelect);
@@ -0,0 +1,124 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ThemeSelect - Components - Kempo Docs - A Web Components Solution</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-vars.css" />
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kempo-css@1.3.11/dist/kempo.min.css" />
11
+ <link rel="stylesheet" href="../kempo-hljs.css" />
12
+ <link rel="stylesheet" href="../styles.css" />
13
+ <script>window.litDisableBundleWarning = true;</script>
14
+ </head>
15
+ <body>
16
+ <k-import src="../nav-1.inc.html"></k-import>
17
+ <h1 class="ta-center">ThemeSelect</h1>
18
+ <main>
19
+ <k-accordion persistent-id="toc" class="b r mb">
20
+ <k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
21
+ <k-accordion-panel name="toc-panel">
22
+ <div class="m pl">
23
+ <h6>Examples</h6>
24
+ <a href="#basicUsage">Basic Usage</a><br />
25
+ <a href="#withLabel">With Label</a><br />
26
+
27
+ <h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
28
+ <a href="#constructor">Constructor</a><br />
29
+ <a href="#requirements">Requirements</a><br />
30
+ <a href="#properties">Properties</a><br />
31
+ <a href="#slots">Slots</a><br />
32
+ <a href="#staticMethods">Static Methods</a><br />
33
+ </div>
34
+ </k-accordion-panel>
35
+ </k-accordion>
36
+
37
+ <h3>Description</h3>
38
+ <p>The <code>ThemeSelect</code> component provides a dropdown select for users to choose between Light, Dark, and System Default themes. It extends the <a href="./shadow-component.html">ShadowComponent</a> class and uses the <a href="../utils/theme.html">theme utility</a> internally for state management, persistence, and synchronization.</p>
39
+ <p>If child content is provided, it renders as a label above the select. If no children are provided, no label is rendered.</p>
40
+ <p><strong>Note:</strong> You can manage theme state without this component by directly importing the <a href="../utils/theme.html">theme utility</a>.</p>
41
+
42
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
43
+ <p>Use the <code>ThemeSelect</code> component without any children to render just the select dropdown.</p>
44
+
45
+ <div class="row -mx mb">
46
+ <div class="col d-span-6 m-span-12 px">
47
+ <k-card label="HTML">
48
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-theme-select</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-theme-select</span>&gt;</span></code></pre>
49
+ </k-card>
50
+ </div>
51
+ <div class="col d-span-6 m-span-12 px">
52
+ <k-card label="Output">
53
+ <k-theme-select></k-theme-select>
54
+ </k-card>
55
+ </div>
56
+ </div>
57
+
58
+ <h3 id="withLabel"><a href="#withLabel" class="no-link">With Label</a></h3>
59
+ <p>Provide child content to render a label above the select.</p>
60
+
61
+ <div class="row -mx mb">
62
+ <div class="col d-span-6 m-span-12 px">
63
+ <k-card label="HTML">
64
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-theme-select</span>&gt;</span>Theme<span class="hljs-tag">&lt;/<span class="hljs-name">k-theme-select</span>&gt;</span></code></pre>
65
+ </k-card>
66
+ </div>
67
+ <div class="col d-span-6 m-span-12 px">
68
+ <k-card label="Output">
69
+ <k-theme-select>Theme</k-theme-select>
70
+ </k-card>
71
+ </div>
72
+ </div>
73
+
74
+ <h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
75
+
76
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
77
+ <h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
78
+ <h5>
79
+ <code>new ThemeSelect()</code>
80
+ </h5>
81
+
82
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
83
+ <ul>
84
+ <li><a href="./shadow-component.html">ShadowComponent</a></li>
85
+ <li><a href="../utils/theme.html">theme</a> utility</li>
86
+ </ul>
87
+
88
+ <h3 id="properties"><a href="#properties" class="no-link">Properties</a></h3>
89
+ <h5><code>currentTheme<i>: string</i></code></h5>
90
+ <p>Gets or sets the current theme. Possible values are <code>"auto"</code>, <code>"light"</code>, or <code>"dark"</code>. This property is reactive and will trigger a re-render when changed. Syncs to <code>current-theme</code> attribute.</p>
91
+
92
+ <h3 id="slots"><a href="#slots" class="no-link">Slots</a></h3>
93
+ <h5><code>default</code></h5>
94
+ <p>The default slot renders as a label above the select. If no content is provided, no label is rendered.</p>
95
+
96
+ <h3 id="staticMethods"><a href="#staticMethods" class="no-link">Static Methods</a></h3>
97
+ <p><strong>Note:</strong> These static methods are convenience wrappers around the <a href="../utils/theme.html">theme utility</a>. For direct theme management without the component, use the theme utility directly.</p>
98
+ <h5><code>static setTheme(theme)<i>: void</i></code></h5>
99
+ <p>Sets the specified theme. Internally calls the <a href="../utils/theme.html">theme utility</a> which handles persistence and DOM updates.</p>
100
+ <ul>
101
+ <li><strong>theme</strong> <code>string</code> - The theme to set. Must be one of: <code>"auto"</code>, <code>"light"</code>, or <code>"dark"</code></li>
102
+ </ul>
103
+ <pre><code class="hljs javascript"><span class="hljs-title class_">ThemeSelect</span>.<span class="hljs-title function_">setTheme</span>(<span class="hljs-string">&#x27;dark&#x27;</span>);</code></pre>
104
+
105
+ <h5><code>static getCurrentTheme()<i>: string</i></code></h5>
106
+ <p>Gets the current theme. Returns <code>"auto"</code> if no theme is set. Internally calls the <a href="../utils/theme.html">theme utility</a>.</p>
107
+ <pre><code class="hljs javascript"><span class="hljs-keyword">const</span> currentTheme = <span class="hljs-title class_">ThemeSelect</span>.<span class="hljs-title function_">getCurrentTheme</span>();<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(currentTheme);</code></pre>
108
+
109
+ <h5><code>static getCalculatedCurrentTheme()<i>: string</i></code></h5>
110
+ <p>Returns the effective theme, always <code>"dark"</code> or <code>"light"</code>. If the current theme is <code>"auto"</code>, this method uses the user's system preference (<code>prefers-color-scheme</code> media query) to determine the theme. Internally calls the <a href="../utils/theme.html">theme utility</a>.</p>
111
+ <pre><code class="hljs javascript"><span class="hljs-keyword">const</span> effectiveTheme = <span class="hljs-title class_">ThemeSelect</span>.<span class="hljs-title function_">getCalculatedCurrentTheme</span>();<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(effectiveTheme);</code></pre>
112
+
113
+ <h3>Related</h3>
114
+ <p>See the <a href="../utils/theme.html">theme utility</a> documentation for programmatic theme management without using this component.</p>
115
+ <p>See the <a href="./theme-switcher.html">ThemeSwitcher</a> component for a button-based toggle alternative.</p>
116
+
117
+ </main>
118
+ <div style="height:33vh"></div>
119
+ <script type="module" src="../src/components/Import.js"></script>
120
+ <script type="module" src="../src/components/ThemeSelect.js"></script>
121
+ <script type="module" src="../src/components/Accordion.js"></script>
122
+ <script type="module" src="../src/components/Card.js"></script>
123
+ </body>
124
+ </html>
package/docs/index.html CHANGED
@@ -185,6 +185,12 @@
185
185
  <p class="tc-muted">Notification messages that appear temporarily at screen corners.</p>
186
186
  </a>
187
187
  </div>
188
+ <div class="span-12 t-span-6 d-span-4 px">
189
+ <a href="./components/theme-select.html" class="card mb no-link d-b">
190
+ <h3 class="tc-primary">Theme Select</h3>
191
+ <p class="tc-muted">Dropdown select for choosing between Light, Dark, and System Default themes.</p>
192
+ </a>
193
+ </div>
188
194
  <div class="span-12 t-span-6 d-span-4 px">
189
195
  <a href="./components/theme-switcher.html" class="card mb no-link d-b">
190
196
  <h3 class="tc-primary">Theme Switcher</h3>
@@ -55,6 +55,7 @@
55
55
  <k-filter-item filter-keywords="tabs tab panel components"><a href="../components/tabs.html">Tabs<br><small>Component</small></a></k-filter-item>
56
56
  <k-filter-item filter-keywords="tags tag input components"><a href="../components/tags.html">Tags<br><small>Component</small></a></k-filter-item>
57
57
  <k-filter-item filter-keywords="toast notification alert components"><a href="../components/toast.html">Toast<br><small>Component</small></a></k-filter-item>
58
+ <k-filter-item filter-keywords="theme select dropdown dark light components"><a href="../components/theme-select.html">Theme Select<br><small>Component</small></a></k-filter-item>
58
59
  <k-filter-item filter-keywords="theme switcher dark light components"><a href="../components/theme-switcher.html">Theme Switcher<br><small>Component</small></a></k-filter-item>
59
60
  <k-filter-item filter-keywords="timestamp date time components"><a href="../components/timestamp.html">Timestamp<br><small>Component</small></a></k-filter-item>
60
61
  <k-filter-item filter-keywords="toggle switch checkbox components"><a href="../components/toggle.html">Toggle<br><small>Component</small></a></k-filter-item>
@@ -115,6 +116,7 @@
115
116
  <a href="../components/tabs.html">Tabs</a>
116
117
  <a href="../components/tags.html">Tags</a>
117
118
  <a href="../components/toast.html">Toast</a>
119
+ <a href="../components/theme-select.html">Theme Select</a>
118
120
  <a href="../components/theme-switcher.html">Theme Switcher</a>
119
121
  <a href="../components/timestamp.html">Timestamp</a>
120
122
  <a href="../components/toggle.html">Toggle</a>
package/docs/nav.inc.html CHANGED
@@ -55,6 +55,7 @@
55
55
  <k-filter-item filter-keywords="tabs tab panel components"><a href="./components/tabs.html">Tabs<br><small>Component</small></a></k-filter-item>
56
56
  <k-filter-item filter-keywords="tags tag input components"><a href="./components/tags.html">Tags<br><small>Component</small></a></k-filter-item>
57
57
  <k-filter-item filter-keywords="toast notification alert components"><a href="./components/toast.html">Toast<br><small>Component</small></a></k-filter-item>
58
+ <k-filter-item filter-keywords="theme select dropdown dark light components"><a href="./components/theme-select.html">Theme Select<br><small>Component</small></a></k-filter-item>
58
59
  <k-filter-item filter-keywords="theme switcher dark light components"><a href="./components/theme-switcher.html">Theme Switcher<br><small>Component</small></a></k-filter-item>
59
60
  <k-filter-item filter-keywords="timestamp date time components"><a href="./components/timestamp.html">Timestamp<br><small>Component</small></a></k-filter-item>
60
61
  <k-filter-item filter-keywords="toggle switch checkbox components"><a href="./components/toggle.html">Toggle<br><small>Component</small></a></k-filter-item>
@@ -114,6 +115,7 @@
114
115
  <a href="./components/tabs.html">Tabs</a>
115
116
  <a href="./components/tags.html">Tags</a>
116
117
  <a href="./components/toast.html">Toast</a>
118
+ <a href="./components/theme-select.html">Theme Select</a>
117
119
  <a href="./components/theme-switcher.html">Theme Switcher</a>
118
120
  <a href="./components/timestamp.html">Timestamp</a>
119
121
  <a href="./components/toggle.html">Toggle</a>
@@ -0,0 +1,8 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import theme from"../utils/theme.js";export default class ThemeSelect extends ShadowComponent{static properties={currentTheme:{type:String,reflect:!0,attribute:"current-theme"}};constructor(){super(),this.currentTheme=theme.get()}handleChange=e=>{theme.set(e.target.value)};connectedCallback(){super.connectedCallback(),this.unsubscribe=theme.subscribe(e=>{this.currentTheme=e})}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribe&&this.unsubscribe()}render(){const e=this.childNodes.length>0;return html`
2
+ ${e?html`<label><slot></slot></label>`:""}
3
+ <select @change=${this.handleChange} .value=${this.currentTheme}>
4
+ <option value="light">Light</option>
5
+ <option value="dark">Dark</option>
6
+ <option value="auto">System Default</option>
7
+ </select>
8
+ `}static setTheme(e){theme.set(e)}static getCurrentTheme(){return theme.get()}static getCalculatedCurrentTheme(){return theme.getCalculated()}}customElements.define("k-theme-select",ThemeSelect);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "A Lit based web-component library",
6
6
  "main": "index.js",
@@ -0,0 +1,68 @@
1
+ import ShadowComponent from './ShadowComponent.js';
2
+ import { html, css } from '../lit-all.min.js';
3
+ import theme from '../utils/theme.js';
4
+
5
+ export default class ThemeSelect extends ShadowComponent {
6
+ static properties = {
7
+ currentTheme: { type: String, reflect: true, attribute: 'current-theme' }
8
+ };
9
+
10
+ constructor() {
11
+ super();
12
+ this.currentTheme = theme.get();
13
+ }
14
+
15
+ /*
16
+ Event Handlers
17
+ */
18
+ handleChange = e => {
19
+ theme.set(e.target.value);
20
+ }
21
+
22
+ /*
23
+ Lifecycle Callbacks
24
+ */
25
+ connectedCallback() {
26
+ super.connectedCallback();
27
+ this.unsubscribe = theme.subscribe(t => {
28
+ this.currentTheme = t;
29
+ });
30
+ }
31
+
32
+ disconnectedCallback() {
33
+ super.disconnectedCallback();
34
+ if(this.unsubscribe) this.unsubscribe();
35
+ }
36
+
37
+ /*
38
+ Rendering
39
+ */
40
+ render() {
41
+ const hasLabel = this.childNodes.length > 0;
42
+ return html`
43
+ ${hasLabel ? html`<label><slot></slot></label>` : ''}
44
+ <select @change=${this.handleChange} .value=${this.currentTheme}>
45
+ <option value="light">Light</option>
46
+ <option value="dark">Dark</option>
47
+ <option value="auto">System Default</option>
48
+ </select>
49
+ `;
50
+ }
51
+
52
+ /*
53
+ Static Methods
54
+ */
55
+ static setTheme(t) {
56
+ theme.set(t);
57
+ }
58
+
59
+ static getCurrentTheme() {
60
+ return theme.get();
61
+ }
62
+
63
+ static getCalculatedCurrentTheme() {
64
+ return theme.getCalculated();
65
+ }
66
+ }
67
+
68
+ customElements.define('k-theme-select', ThemeSelect);
@@ -302,6 +302,7 @@ export default {
302
302
 
303
303
  'should close on Escape key': async ({pass, fail}) => {
304
304
  const { container, dropdown } = await createDropdown({ opened: true });
305
+ dropdown.querySelector('button[data-value]').focus();
305
306
  const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
306
307
  document.dispatchEvent(event);
307
308
  if(dropdown.opened !== false) {
@@ -0,0 +1,408 @@
1
+ import ThemeSelect from '../../src/components/ThemeSelect.js';
2
+ import theme from '../../src/utils/theme.js';
3
+
4
+ const createThemeSelect = async (innerHTML = '') => {
5
+ const container = document.createElement('div');
6
+ container.innerHTML = `<k-theme-select>${innerHTML}</k-theme-select>`;
7
+ document.body.appendChild(container);
8
+
9
+ const select = container.querySelector('k-theme-select');
10
+ await select.updateComplete;
11
+
12
+ return { container, select };
13
+ };
14
+
15
+ const cleanup = (container) => {
16
+ if(container && container.parentNode){
17
+ container.parentNode.removeChild(container);
18
+ }
19
+ theme.set('auto');
20
+ };
21
+
22
+ export default {
23
+ /*
24
+ ThemeSelect Component Tests
25
+ */
26
+ 'should create theme-select element': async ({pass, fail}) => {
27
+ const { container, select } = await createThemeSelect();
28
+
29
+ if(!select){
30
+ cleanup(container);
31
+ fail('ThemeSelect element should be created');
32
+ return;
33
+ }
34
+
35
+ if(!(select instanceof ThemeSelect)){
36
+ cleanup(container);
37
+ fail('Element should be instance of ThemeSelect');
38
+ return;
39
+ }
40
+
41
+ cleanup(container);
42
+ pass('ThemeSelect element created correctly');
43
+ },
44
+
45
+ 'should have shadow root': async ({pass, fail}) => {
46
+ const { container, select } = await createThemeSelect();
47
+
48
+ if(!select.shadowRoot){
49
+ cleanup(container);
50
+ fail('ThemeSelect should have shadow root');
51
+ return;
52
+ }
53
+
54
+ cleanup(container);
55
+ pass('ThemeSelect has shadow root');
56
+ },
57
+
58
+ 'should have currentTheme property': async ({pass, fail}) => {
59
+ const { container, select } = await createThemeSelect();
60
+
61
+ if(typeof select.currentTheme !== 'string'){
62
+ cleanup(container);
63
+ fail('ThemeSelect should have currentTheme property');
64
+ return;
65
+ }
66
+
67
+ cleanup(container);
68
+ pass('ThemeSelect has currentTheme property');
69
+ },
70
+
71
+ 'should reflect current-theme attribute': async ({pass, fail}) => {
72
+ const { container, select } = await createThemeSelect();
73
+
74
+ const currentTheme = select.getAttribute('current-theme');
75
+ if(!currentTheme){
76
+ cleanup(container);
77
+ fail('current-theme attribute should be set');
78
+ return;
79
+ }
80
+
81
+ cleanup(container);
82
+ pass('current-theme attribute reflects correctly');
83
+ },
84
+
85
+ 'should render select element': async ({pass, fail}) => {
86
+ const { container, select } = await createThemeSelect();
87
+
88
+ const selectEl = select.shadowRoot.querySelector('select');
89
+ if(!selectEl){
90
+ cleanup(container);
91
+ fail('ThemeSelect should render a select element');
92
+ return;
93
+ }
94
+
95
+ cleanup(container);
96
+ pass('ThemeSelect renders select element');
97
+ },
98
+
99
+ 'should render three options': async ({pass, fail}) => {
100
+ const { container, select } = await createThemeSelect();
101
+
102
+ const options = select.shadowRoot.querySelectorAll('option');
103
+ if(options.length !== 3){
104
+ cleanup(container);
105
+ fail(`Expected 3 options, got ${options.length}`);
106
+ return;
107
+ }
108
+
109
+ cleanup(container);
110
+ pass('ThemeSelect renders three options');
111
+ },
112
+
113
+ 'should have correct option values': async ({pass, fail}) => {
114
+ const { container, select } = await createThemeSelect();
115
+
116
+ const options = select.shadowRoot.querySelectorAll('option');
117
+ const values = Array.from(options).map(o => o.value);
118
+
119
+ if(!values.includes('light') || !values.includes('dark') || !values.includes('auto')){
120
+ cleanup(container);
121
+ fail(`Expected options with values light, dark, auto. Got ${values.join(', ')}`);
122
+ return;
123
+ }
124
+
125
+ cleanup(container);
126
+ pass('Options have correct values');
127
+ },
128
+
129
+ 'should have correct option labels': async ({pass, fail}) => {
130
+ const { container, select } = await createThemeSelect();
131
+
132
+ const options = select.shadowRoot.querySelectorAll('option');
133
+ const labels = Array.from(options).map(o => o.textContent);
134
+
135
+ if(!labels.includes('Light') || !labels.includes('Dark') || !labels.includes('System Default')){
136
+ cleanup(container);
137
+ fail(`Expected labels Light, Dark, System Default. Got ${labels.join(', ')}`);
138
+ return;
139
+ }
140
+
141
+ cleanup(container);
142
+ pass('Options have correct labels');
143
+ },
144
+
145
+ 'should not render label when no children': async ({pass, fail}) => {
146
+ const { container, select } = await createThemeSelect();
147
+
148
+ const label = select.shadowRoot.querySelector('label');
149
+ if(label){
150
+ cleanup(container);
151
+ fail('ThemeSelect should not render label when no children provided');
152
+ return;
153
+ }
154
+
155
+ cleanup(container);
156
+ pass('No label rendered without children');
157
+ },
158
+
159
+ 'should render label when children provided': async ({pass, fail}) => {
160
+ const { container, select } = await createThemeSelect('Theme');
161
+
162
+ const label = select.shadowRoot.querySelector('label');
163
+ if(!label){
164
+ cleanup(container);
165
+ fail('ThemeSelect should render label when children are provided');
166
+ return;
167
+ }
168
+
169
+ cleanup(container);
170
+ pass('Label rendered with children');
171
+ },
172
+
173
+ 'should set theme on select change to light': async ({pass, fail}) => {
174
+ const { container, select } = await createThemeSelect();
175
+
176
+ theme.set('auto');
177
+ await select.updateComplete;
178
+
179
+ const selectEl = select.shadowRoot.querySelector('select');
180
+ selectEl.value = 'light';
181
+ selectEl.dispatchEvent(new Event('change'));
182
+ await select.updateComplete;
183
+
184
+ if(theme.get() !== 'light'){
185
+ cleanup(container);
186
+ fail(`Expected theme "light", got "${theme.get()}"`);
187
+ return;
188
+ }
189
+
190
+ cleanup(container);
191
+ pass('Theme set to light on select change');
192
+ },
193
+
194
+ 'should set theme on select change to dark': async ({pass, fail}) => {
195
+ const { container, select } = await createThemeSelect();
196
+
197
+ theme.set('auto');
198
+ await select.updateComplete;
199
+
200
+ const selectEl = select.shadowRoot.querySelector('select');
201
+ selectEl.value = 'dark';
202
+ selectEl.dispatchEvent(new Event('change'));
203
+ await select.updateComplete;
204
+
205
+ if(theme.get() !== 'dark'){
206
+ cleanup(container);
207
+ fail(`Expected theme "dark", got "${theme.get()}"`);
208
+ return;
209
+ }
210
+
211
+ cleanup(container);
212
+ pass('Theme set to dark on select change');
213
+ },
214
+
215
+ 'should set theme on select change to auto': async ({pass, fail}) => {
216
+ const { container, select } = await createThemeSelect();
217
+
218
+ theme.set('light');
219
+ await select.updateComplete;
220
+
221
+ const selectEl = select.shadowRoot.querySelector('select');
222
+ selectEl.value = 'auto';
223
+ selectEl.dispatchEvent(new Event('change'));
224
+ await select.updateComplete;
225
+
226
+ if(theme.get() !== 'auto'){
227
+ cleanup(container);
228
+ fail(`Expected theme "auto", got "${theme.get()}"`);
229
+ return;
230
+ }
231
+
232
+ cleanup(container);
233
+ pass('Theme set to auto on select change');
234
+ },
235
+
236
+ 'should update currentTheme when theme changes': async ({pass, fail}) => {
237
+ const { container, select } = await createThemeSelect();
238
+
239
+ theme.set('dark');
240
+ await new Promise(r => setTimeout(r, 50));
241
+ await select.updateComplete;
242
+
243
+ if(select.currentTheme !== 'dark'){
244
+ cleanup(container);
245
+ fail(`Expected currentTheme "dark", got "${select.currentTheme}"`);
246
+ return;
247
+ }
248
+
249
+ cleanup(container);
250
+ pass('currentTheme updates when theme changes');
251
+ },
252
+
253
+ 'should update select value when theme changes externally': async ({pass, fail}) => {
254
+ const { container, select } = await createThemeSelect();
255
+
256
+ theme.set('dark');
257
+ await new Promise(r => setTimeout(r, 50));
258
+ await select.updateComplete;
259
+
260
+ const selectEl = select.shadowRoot.querySelector('select');
261
+ if(selectEl.value !== 'dark'){
262
+ cleanup(container);
263
+ fail(`Expected select value "dark", got "${selectEl.value}"`);
264
+ return;
265
+ }
266
+
267
+ cleanup(container);
268
+ pass('Select value syncs with external theme changes');
269
+ },
270
+
271
+ 'should have static setTheme method': async ({pass, fail}) => {
272
+ const { container, select } = await createThemeSelect();
273
+
274
+ if(typeof ThemeSelect.setTheme !== 'function'){
275
+ cleanup(container);
276
+ fail('ThemeSelect should have static setTheme method');
277
+ return;
278
+ }
279
+
280
+ cleanup(container);
281
+ pass('ThemeSelect has static setTheme method');
282
+ },
283
+
284
+ 'static setTheme should set theme': async ({pass, fail}) => {
285
+ const { container, select } = await createThemeSelect();
286
+
287
+ ThemeSelect.setTheme('light');
288
+
289
+ if(theme.get() !== 'light'){
290
+ cleanup(container);
291
+ fail(`Expected theme "light", got "${theme.get()}"`);
292
+ return;
293
+ }
294
+
295
+ cleanup(container);
296
+ pass('Static setTheme sets theme');
297
+ },
298
+
299
+ 'should have static getCurrentTheme method': async ({pass, fail}) => {
300
+ const { container, select } = await createThemeSelect();
301
+
302
+ if(typeof ThemeSelect.getCurrentTheme !== 'function'){
303
+ cleanup(container);
304
+ fail('ThemeSelect should have static getCurrentTheme method');
305
+ return;
306
+ }
307
+
308
+ cleanup(container);
309
+ pass('ThemeSelect has static getCurrentTheme method');
310
+ },
311
+
312
+ 'static getCurrentTheme should return current theme': async ({pass, fail}) => {
313
+ const { container, select } = await createThemeSelect();
314
+
315
+ theme.set('dark');
316
+ const current = ThemeSelect.getCurrentTheme();
317
+
318
+ if(current !== 'dark'){
319
+ cleanup(container);
320
+ fail(`Expected "dark", got "${current}"`);
321
+ return;
322
+ }
323
+
324
+ cleanup(container);
325
+ pass('Static getCurrentTheme returns current theme');
326
+ },
327
+
328
+ 'should have static getCalculatedCurrentTheme method': async ({pass, fail}) => {
329
+ const { container, select } = await createThemeSelect();
330
+
331
+ if(typeof ThemeSelect.getCalculatedCurrentTheme !== 'function'){
332
+ cleanup(container);
333
+ fail('ThemeSelect should have static getCalculatedCurrentTheme method');
334
+ return;
335
+ }
336
+
337
+ cleanup(container);
338
+ pass('ThemeSelect has static getCalculatedCurrentTheme method');
339
+ },
340
+
341
+ 'static getCalculatedCurrentTheme should return light or dark': async ({pass, fail}) => {
342
+ const { container, select } = await createThemeSelect();
343
+
344
+ theme.set('auto');
345
+ const calculated = ThemeSelect.getCalculatedCurrentTheme();
346
+
347
+ if(calculated !== 'light' && calculated !== 'dark'){
348
+ cleanup(container);
349
+ fail(`Expected "light" or "dark", got "${calculated}"`);
350
+ return;
351
+ }
352
+
353
+ cleanup(container);
354
+ pass('Static getCalculatedCurrentTheme returns light or dark');
355
+ },
356
+
357
+ 'should subscribe to theme changes on connect': async ({pass, fail}) => {
358
+ const { container, select } = await createThemeSelect();
359
+
360
+ if(typeof select.unsubscribe !== 'function'){
361
+ cleanup(container);
362
+ fail('ThemeSelect should have unsubscribe function after connect');
363
+ return;
364
+ }
365
+
366
+ cleanup(container);
367
+ pass('ThemeSelect subscribes to theme changes');
368
+ },
369
+
370
+ 'should unsubscribe on disconnect': async ({pass, fail}) => {
371
+ const { container, select } = await createThemeSelect();
372
+
373
+ const originalUnsubscribe = select.unsubscribe;
374
+ let unsubscribeCalled = false;
375
+ select.unsubscribe = () => {
376
+ unsubscribeCalled = true;
377
+ originalUnsubscribe();
378
+ };
379
+
380
+ select.disconnectedCallback();
381
+
382
+ if(!unsubscribeCalled){
383
+ cleanup(container);
384
+ fail('unsubscribe should be called on disconnect');
385
+ return;
386
+ }
387
+
388
+ cleanup(container);
389
+ pass('ThemeSelect unsubscribes on disconnect');
390
+ },
391
+
392
+ 'should sync with external theme changes': async ({pass, fail}) => {
393
+ const { container, select } = await createThemeSelect();
394
+
395
+ theme.set('dark');
396
+ await new Promise(r => setTimeout(r, 50));
397
+ await select.updateComplete;
398
+
399
+ if(select.currentTheme !== 'dark'){
400
+ cleanup(container);
401
+ fail(`Expected currentTheme to sync to "dark", got "${select.currentTheme}"`);
402
+ return;
403
+ }
404
+
405
+ cleanup(container);
406
+ pass('ThemeSelect syncs with external theme changes');
407
+ }
408
+ };