juxscript 1.0.20 → 1.0.21

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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,5 +1,8 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['tabChange'] as const;
3
6
 
4
7
  export interface Tab {
5
8
  id: string;
@@ -24,37 +27,30 @@ type TabsState = {
24
27
  class: string;
25
28
  };
26
29
 
27
- export class Tabs {
28
- state: TabsState;
29
- container: HTMLElement | null = null;
30
- _id: string;
31
- id: string;
32
-
33
- // CRITICAL: Store bind/sync instructions for deferred wiring
34
- private _bindings: Array<{ event: string, handler: Function }> = [];
35
- private _syncBindings: Array<{
36
- property: string,
37
- stateObj: State<any>,
38
- toState?: Function,
39
- toComponent?: Function
40
- }> = [];
41
-
30
+ export class Tabs extends BaseComponent<TabsState> {
42
31
  constructor(id: string, options: TabsOptions = {}) {
43
- this._id = id;
44
- this.id = id;
45
-
46
- this.state = {
32
+ super(id, {
47
33
  tabs: options.tabs ?? [],
48
34
  activeTab: options.activeTab ?? (options.tabs?.[0]?.id ?? ''),
49
35
  variant: options.variant ?? 'default',
50
36
  style: options.style ?? '',
51
37
  class: options.class ?? ''
52
- };
38
+ });
39
+ }
40
+
41
+ protected getTriggerEvents(): readonly string[] {
42
+ return TRIGGER_EVENTS;
43
+ }
44
+
45
+ protected getCallbackEvents(): readonly string[] {
46
+ return CALLBACK_EVENTS;
53
47
  }
54
48
 
55
- /* -------------------------
56
- * Fluent API
57
- * ------------------------- */
49
+ /* ═════════════════════════════════════════════════════════════════
50
+ * FLUENT API
51
+ * ═════════════════════════════════════════════════════════════════ */
52
+
53
+ // ✅ Inherited from BaseComponent
58
54
 
59
55
  tabs(value: Tab[]): this {
60
56
  this.state.tabs = value;
@@ -77,64 +73,32 @@ export class Tabs {
77
73
  return this;
78
74
  }
79
75
 
80
- style(value: string): this {
81
- this.state.style = value;
82
- return this;
83
- }
84
-
85
- class(value: string): this {
86
- this.state.class = value;
87
- return this;
88
- }
89
-
90
- bind(event: string, handler: Function): this {
91
- this._bindings.push({ event, handler });
92
- return this;
93
- }
94
-
95
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
96
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
97
- throw new Error(`Tabs.sync: Expected a State object for property "${property}"`);
98
- }
99
- this._syncBindings.push({ property, stateObj, toState, toComponent });
100
- return this;
101
- }
102
-
103
76
  private _updateActiveTab(): void {
104
77
  if (!this.container) return;
105
78
  const wrapper = this.container.querySelector(`#${this._id}`);
106
79
  if (!wrapper) return;
107
80
 
108
- // Update tab buttons
109
- wrapper.querySelectorAll('.jux-tab-button').forEach(btn => {
110
- btn.classList.toggle('active', btn.getAttribute('data-tab-id') === this.state.activeTab);
81
+ wrapper.querySelectorAll('.jux-tabs-button').forEach(btn => {
82
+ btn.classList.toggle('jux-tabs-button-active', btn.getAttribute('data-tab') === this.state.activeTab);
111
83
  });
112
84
 
113
- // Update tab panels
114
- wrapper.querySelectorAll('.jux-tab-panel').forEach(panel => {
115
- panel.classList.toggle('active', panel.getAttribute('data-tab-id') === this.state.activeTab);
85
+ wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
86
+ const isActive = panel.getAttribute('data-tab') === this.state.activeTab;
87
+ panel.classList.toggle('jux-tabs-panel-active', isActive);
88
+ (panel as HTMLElement).style.display = isActive ? 'block' : 'none';
116
89
  });
117
90
  }
118
91
 
92
+ /* ═════════════════════════════════════════════════════════════════
93
+ * RENDER
94
+ * ═════════════════════════════════════════════════════════════════ */
95
+
119
96
  render(targetId?: string): this {
120
- // === 1. SETUP: Get or create container ===
121
- let container: HTMLElement;
122
- if (targetId) {
123
- const target = document.querySelector(targetId);
124
- if (!target || !(target instanceof HTMLElement)) {
125
- throw new Error(`Tabs: Target "${targetId}" not found`);
126
- }
127
- container = target;
128
- } else {
129
- container = getOrCreateContainer(this._id);
130
- }
131
- this.container = container;
97
+ const container = this._setupContainer(targetId);
132
98
 
133
- // === 2. PREPARE: Destructure state and check sync flags ===
134
99
  const { tabs, activeTab, variant, style, class: className } = this.state;
135
100
  const hasActiveTabSync = this._syncBindings.some(b => b.property === 'activeTab');
136
101
 
137
- // === 3. BUILD: Create DOM elements ===
138
102
  const wrapper = document.createElement('div');
139
103
  wrapper.className = `jux-tabs jux-tabs-${variant}`;
140
104
  wrapper.id = this._id;
@@ -147,8 +111,7 @@ export class Tabs {
147
111
  const tabPanels = document.createElement('div');
148
112
  tabPanels.className = 'jux-tabs-panels';
149
113
 
150
- tabs.forEach((tab, index) => {
151
- // Tab button
114
+ tabs.forEach(tab => {
152
115
  const tabButton = document.createElement('button');
153
116
  tabButton.className = 'jux-tabs-button';
154
117
  tabButton.setAttribute('data-tab', tab.id);
@@ -156,7 +119,6 @@ export class Tabs {
156
119
  tabButton.textContent = tab.label;
157
120
  tabList.appendChild(tabButton);
158
121
 
159
- // Tab panel
160
122
  const tabPanel = document.createElement('div');
161
123
  tabPanel.className = 'jux-tabs-panel';
162
124
  tabPanel.setAttribute('data-tab', tab.id);
@@ -176,9 +138,6 @@ export class Tabs {
176
138
  wrapper.appendChild(tabList);
177
139
  wrapper.appendChild(tabPanels);
178
140
 
179
- // === 4. WIRE: Attach event listeners and sync bindings ===
180
-
181
- // Default tab switching behavior (only if NOT using sync)
182
141
  if (!hasActiveTabSync) {
183
142
  tabList.querySelectorAll('.jux-tabs-button').forEach(button => {
184
143
  button.addEventListener('click', () => {
@@ -189,12 +148,8 @@ export class Tabs {
189
148
  });
190
149
  }
191
150
 
192
- // Wire custom bindings from .bind() calls
193
- this._bindings.forEach(({ event, handler }) => {
194
- wrapper.addEventListener(event, handler as EventListener);
195
- });
151
+ this._wireStandardEvents(wrapper);
196
152
 
197
- // Wire sync bindings from .sync() calls
198
153
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
199
154
  if (property === 'activeTab') {
200
155
  const transformToState = toState || ((v: any) => String(v));
@@ -202,7 +157,6 @@ export class Tabs {
202
157
 
203
158
  let isUpdating = false;
204
159
 
205
- // State → Component
206
160
  stateObj.subscribe((val: any) => {
207
161
  if (isUpdating) return;
208
162
  const transformed = transformToComponent(val);
@@ -210,7 +164,6 @@ export class Tabs {
210
164
  this._switchTab(transformed, wrapper);
211
165
  });
212
166
 
213
- // Component → State (tab clicks)
214
167
  tabList.querySelectorAll('.jux-tabs-button').forEach(button => {
215
168
  button.addEventListener('click', () => {
216
169
  if (isUpdating) return;
@@ -228,13 +181,12 @@ export class Tabs {
228
181
  });
229
182
  }
230
183
  else if (property === 'tabs') {
231
- const transformToComponent = toComponent || ((v: any) => v);
184
+ const transform = toComponent || ((v: any) => v);
232
185
 
233
186
  stateObj.subscribe((val: any) => {
234
- const transformed = transformToComponent(val);
187
+ const transformed = transform(val);
235
188
  this.state.tabs = transformed;
236
189
 
237
- // Re-render tabs
238
190
  tabList.innerHTML = '';
239
191
  tabPanels.innerHTML = '';
240
192
 
@@ -262,7 +214,6 @@ export class Tabs {
262
214
  tabPanels.appendChild(tabPanel);
263
215
  });
264
216
 
265
- // Re-wire click handlers
266
217
  tabList.querySelectorAll('.jux-tabs-button').forEach(button => {
267
218
  button.addEventListener('click', () => {
268
219
  const tabId = button.getAttribute('data-tab')!;
@@ -274,38 +225,23 @@ export class Tabs {
274
225
  }
275
226
  });
276
227
 
277
- // === 5. RENDER: Append to DOM and finalize ===
278
228
  container.appendChild(wrapper);
279
229
  return this;
280
230
  }
281
231
 
282
232
  private _switchTab(tabId: string, wrapper: HTMLElement): void {
283
- // Update buttons
284
233
  wrapper.querySelectorAll('.jux-tabs-button').forEach(btn => {
285
- if (btn.getAttribute('data-tab') === tabId) {
286
- btn.classList.add('jux-tabs-button-active');
287
- } else {
288
- btn.classList.remove('jux-tabs-button-active');
289
- }
234
+ btn.classList.toggle('jux-tabs-button-active', btn.getAttribute('data-tab') === tabId);
290
235
  });
291
236
 
292
- // Update panels
293
237
  wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
294
- if (panel.getAttribute('data-tab') === tabId) {
295
- panel.classList.add('jux-tabs-panel-active');
296
- (panel as HTMLElement).style.display = 'block';
297
- } else {
298
- panel.classList.remove('jux-tabs-panel-active');
299
- (panel as HTMLElement).style.display = 'none';
300
- }
238
+ const isActive = panel.getAttribute('data-tab') === tabId;
239
+ panel.classList.toggle('jux-tabs-panel-active', isActive);
240
+ (panel as HTMLElement).style.display = isActive ? 'block' : 'none';
301
241
  });
302
- }
303
242
 
304
- renderTo(juxComponent: any): this {
305
- if (!juxComponent?._id) {
306
- throw new Error('Tabs.renderTo: Invalid component');
307
- }
308
- return this.render(`#${juxComponent._id}`);
243
+ // 🎯 Fire the tabChange callback event
244
+ this._triggerCallback('tabChange', tabId);
309
245
  }
310
246
  }
311
247
 
@@ -1,19 +1,17 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
2
  import { ErrorHandler } from './error-handler.js';
3
3
  import { renderIcon } from './icons.js';
4
4
 
5
- /**
6
- * Theme configuration
7
- */
5
+ // Event definitions
6
+ const TRIGGER_EVENTS = [] as const;
7
+ const CALLBACK_EVENTS = ['themeChange'] as const;
8
+
8
9
  export interface Theme {
9
10
  id: string;
10
11
  label: string;
11
12
  icon?: string;
12
13
  }
13
14
 
14
- /**
15
- * ThemeToggle component options
16
- */
17
15
  export interface ThemeToggleOptions {
18
16
  themes?: Theme[];
19
17
  defaultTheme?: string;
@@ -24,9 +22,6 @@ export interface ThemeToggleOptions {
24
22
  class?: string;
25
23
  }
26
24
 
27
- /**
28
- * ThemeToggle component state
29
- */
30
25
  type ThemeToggleState = {
31
26
  themes: Theme[];
32
27
  currentTheme: string;
@@ -37,71 +32,48 @@ type ThemeToggleState = {
37
32
  class: string;
38
33
  };
39
34
 
40
- /**
41
- * ThemeToggle component - Manage and switch between themes
42
- *
43
- * Usage:
44
- * // Simple light/dark toggle
45
- * jux.themeToggle('myToggle').render('#appheader-actions');
46
- *
47
- * // Custom themes
48
- * jux.themeToggle('myToggle', {
49
- * themes: [
50
- * { id: 'light', label: 'Light', icon: '☀️' },
51
- * { id: 'dark', label: 'Dark', icon: '🌙' },
52
- * { id: 'auto', label: 'Auto', icon: '🌓' }
53
- * ],
54
- * variant: 'dropdown'
55
- * }).render('#appheader-actions');
56
- */
57
- export class ThemeToggle {
58
- state: ThemeToggleState;
59
- container: HTMLElement | null = null;
60
- _id: string;
61
- id: string;
62
-
35
+ export class ThemeToggle extends BaseComponent<ThemeToggleState> {
63
36
  constructor(id: string, options: ThemeToggleOptions = {}) {
64
- this._id = id;
65
- this.id = id;
66
-
67
37
  const defaultThemes: Theme[] = [
68
38
  { id: 'light', label: 'Light', icon: '☀️' },
69
39
  { id: 'dark', label: 'Dark', icon: '🌙' }
70
40
  ];
71
41
 
72
- this.state = {
42
+ super(id, {
73
43
  themes: options.themes ?? defaultThemes,
74
- currentTheme: options.defaultTheme ?? this.detectTheme(options.storageKey),
44
+ currentTheme: options.defaultTheme ?? ThemeToggle.detectTheme(options.storageKey),
75
45
  storageKey: options.storageKey ?? 'jux-theme',
76
46
  showLabel: options.showLabel ?? false,
77
47
  variant: options.variant ?? 'button',
78
48
  style: options.style ?? '',
79
49
  class: options.class ?? ''
80
- };
50
+ });
81
51
 
82
52
  // Apply theme on initialization
83
53
  this.applyTheme(this.state.currentTheme);
84
54
  }
85
55
 
86
- /* -------------------------
87
- * Theme Detection & Management
88
- * ------------------------- */
56
+ protected getTriggerEvents(): readonly string[] {
57
+ return TRIGGER_EVENTS;
58
+ }
89
59
 
90
- /**
91
- * Detect theme from localStorage or system preference
92
- */
93
- private detectTheme(storageKey = 'jux-theme'): string {
60
+ protected getCallbackEvents(): readonly string[] {
61
+ return CALLBACK_EVENTS;
62
+ }
63
+
64
+ /* ═════════════════════════════════════════════════════════════════
65
+ * STATIC METHODS
66
+ * ═════════════════════════════════════════════════════════════════ */
67
+
68
+ private static detectTheme(storageKey = 'jux-theme'): string {
94
69
  if (typeof window === 'undefined') return 'light';
95
70
 
96
- // Check localStorage first
97
71
  const stored = localStorage.getItem(storageKey);
98
72
  if (stored) return stored;
99
73
 
100
- // Check current document attribute
101
74
  const current = document.body.getAttribute('data-theme');
102
75
  if (current) return current;
103
76
 
104
- // Check system preference
105
77
  if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
106
78
  return 'dark';
107
79
  }
@@ -109,58 +81,27 @@ export class ThemeToggle {
109
81
  return 'light';
110
82
  }
111
83
 
112
- /**
113
- * Apply theme to document
114
- */
115
- private applyTheme(themeId: string): void {
116
- if (typeof document === 'undefined') return;
84
+ /* ═════════════════════════════════════════════════════════════════
85
+ * FLUENT API
86
+ * ═════════════════════════════════════════════════════════════════ */
117
87
 
118
- try {
119
- // Update data-theme attribute on body
120
- document.body.setAttribute('data-theme', themeId);
88
+ // ✅ Inherited from BaseComponent
121
89
 
122
- // Also update on html element for broader CSS support
123
- document.documentElement.setAttribute('data-theme', themeId);
124
-
125
- // Store in localStorage
126
- localStorage.setItem(this.state.storageKey, themeId);
127
-
128
- // Dispatch custom event for other components to listen
129
- window.dispatchEvent(new CustomEvent('themechange', {
130
- detail: { theme: themeId }
131
- }));
132
-
133
- console.log(`🎨 Theme applied: ${themeId}`);
134
- } catch (error: any) {
135
- ErrorHandler.captureError({
136
- component: 'ThemeToggle',
137
- method: 'applyTheme',
138
- message: `Failed to apply theme: ${error.message}`,
139
- stack: error.stack,
140
- timestamp: new Date(),
141
- context: { themeId }
142
- });
143
- }
90
+ themes(value: Theme[]): this {
91
+ this.state.themes = value;
92
+ return this;
144
93
  }
145
94
 
146
- /**
147
- * Cycle to next theme
148
- */
149
- private cycleTheme(): void {
150
- const currentIndex = this.state.themes.findIndex(t => t.id === this.state.currentTheme);
151
- const nextIndex = (currentIndex + 1) % this.state.themes.length;
152
- const nextTheme = this.state.themes[nextIndex];
153
-
154
- this.setTheme(nextTheme.id);
95
+ variant(value: 'button' | 'dropdown' | 'cycle'): this {
96
+ this.state.variant = value;
97
+ return this;
155
98
  }
156
99
 
157
- /* -------------------------
158
- * Public API
159
- * ------------------------- */
100
+ showLabel(value: boolean): this {
101
+ this.state.showLabel = value;
102
+ return this;
103
+ }
160
104
 
161
- /**
162
- * Set current theme
163
- */
164
105
  setTheme(themeId: string): this {
165
106
  const theme = this.state.themes.find(t => t.id === themeId);
166
107
 
@@ -176,16 +117,10 @@ export class ThemeToggle {
176
117
  return this;
177
118
  }
178
119
 
179
- /**
180
- * Get current theme
181
- */
182
120
  getTheme(): string {
183
121
  return this.state.currentTheme;
184
122
  }
185
123
 
186
- /**
187
- * Add a theme
188
- */
189
124
  addTheme(theme: Theme): this {
190
125
  if (!this.state.themes.find(t => t.id === theme.id)) {
191
126
  this.state.themes.push(theme);
@@ -193,38 +128,45 @@ export class ThemeToggle {
193
128
  return this;
194
129
  }
195
130
 
196
- /* -------------------------
197
- * Fluent API
198
- * ------------------------- */
131
+ /* ═════════════════════════════════════════════════════════════════
132
+ * PRIVATE METHODS
133
+ * ═════════════════════════════════════════════════════════════════ */
199
134
 
200
- themes(value: Theme[]): this {
201
- this.state.themes = value;
202
- return this;
203
- }
135
+ private applyTheme(themeId: string): void {
136
+ if (typeof document === 'undefined') return;
204
137
 
205
- variant(value: 'button' | 'dropdown' | 'cycle'): this {
206
- this.state.variant = value;
207
- return this;
208
- }
138
+ try {
139
+ document.body.setAttribute('data-theme', themeId);
140
+ document.documentElement.setAttribute('data-theme', themeId);
141
+ localStorage.setItem(this.state.storageKey, themeId);
209
142
 
210
- showLabel(value: boolean): this {
211
- this.state.showLabel = value;
212
- return this;
213
- }
143
+ window.dispatchEvent(new CustomEvent('themechange', {
144
+ detail: { theme: themeId }
145
+ }));
214
146
 
215
- style(value: string): this {
216
- this.state.style = value;
217
- return this;
218
- }
147
+ // 🎯 Fire the themeChange callback event
148
+ this._triggerCallback('themeChange', themeId);
219
149
 
220
- class(value: string): this {
221
- this.state.class = value;
222
- return this;
150
+ console.log(`🎨 Theme applied: ${themeId}`);
151
+ } catch (error: any) {
152
+ ErrorHandler.captureError({
153
+ component: 'ThemeToggle',
154
+ method: 'applyTheme',
155
+ message: `Failed to apply theme: ${error.message}`,
156
+ stack: error.stack,
157
+ timestamp: new Date(),
158
+ context: { themeId }
159
+ });
160
+ }
223
161
  }
224
162
 
225
- /* -------------------------
226
- * DOM Update
227
- * ------------------------- */
163
+ private cycleTheme(): void {
164
+ const currentIndex = this.state.themes.findIndex(t => t.id === this.state.currentTheme);
165
+ const nextIndex = (currentIndex + 1) % this.state.themes.length;
166
+ const nextTheme = this.state.themes[nextIndex];
167
+
168
+ this.setTheme(nextTheme.id);
169
+ }
228
170
 
229
171
  private _updateDOM(): void {
230
172
  if (!this.container) return;
@@ -235,7 +177,6 @@ export class ThemeToggle {
235
177
  const currentTheme = this.state.themes.find(t => t.id === this.state.currentTheme);
236
178
  if (!currentTheme) return;
237
179
 
238
- // Update button variant
239
180
  if (this.state.variant === 'button' || this.state.variant === 'cycle') {
240
181
  const button = toggle.querySelector('button');
241
182
  if (button && currentTheme.icon) {
@@ -249,7 +190,6 @@ export class ThemeToggle {
249
190
  button.appendChild(labelSpan);
250
191
  }
251
192
 
252
- // Trigger Lucide rendering
253
193
  requestAnimationFrame(() => {
254
194
  if ((window as any).lucide) {
255
195
  (window as any).lucide.createIcons();
@@ -258,7 +198,6 @@ export class ThemeToggle {
258
198
  }
259
199
  }
260
200
 
261
- // Update dropdown variant
262
201
  if (this.state.variant === 'dropdown') {
263
202
  const select = toggle.querySelector('select') as HTMLSelectElement;
264
203
  if (select) {
@@ -267,24 +206,13 @@ export class ThemeToggle {
267
206
  }
268
207
  }
269
208
 
270
- /* -------------------------
271
- * Render
272
- * ------------------------- */
209
+ /* ═════════════════════════════════════════════════════════════════
210
+ * RENDER
211
+ * ═════════════════════════════════════════════════════════════════ */
273
212
 
274
213
  render(targetId?: string): this {
275
- let container: HTMLElement;
276
-
277
- if (targetId) {
278
- const target = document.querySelector(targetId);
279
- if (!target || !(target instanceof HTMLElement)) {
280
- throw new Error(`ThemeToggle: Target element "${targetId}" not found`);
281
- }
282
- container = target;
283
- } else {
284
- container = getOrCreateContainer(this._id);
285
- }
214
+ const container = this._setupContainer(targetId);
286
215
 
287
- this.container = container;
288
216
  const { themes, currentTheme, showLabel, variant, style, class: className } = this.state;
289
217
 
290
218
  const wrapper = document.createElement('div');
@@ -301,9 +229,7 @@ export class ThemeToggle {
301
229
 
302
230
  const theme = themes.find(t => t.id === currentTheme);
303
231
 
304
- // Render based on variant
305
232
  if (variant === 'button' || variant === 'cycle') {
306
- // Single button that cycles through themes
307
233
  const button = document.createElement('button');
308
234
  button.className = 'jux-theme-toggle-button';
309
235
  button.type = 'button';
@@ -329,7 +255,6 @@ export class ThemeToggle {
329
255
  wrapper.appendChild(button);
330
256
 
331
257
  } else if (variant === 'dropdown') {
332
- // Dropdown select with all themes
333
258
  const select = document.createElement('select');
334
259
  select.className = 'jux-theme-toggle-select';
335
260
  select.setAttribute('aria-label', 'Select theme');
@@ -356,9 +281,10 @@ export class ThemeToggle {
356
281
  wrapper.appendChild(select);
357
282
  }
358
283
 
284
+ this._wireStandardEvents(wrapper);
285
+
359
286
  container.appendChild(wrapper);
360
287
 
361
- // Trigger Lucide icon rendering
362
288
  requestAnimationFrame(() => {
363
289
  if ((window as any).lucide) {
364
290
  (window as any).lucide.createIcons();
@@ -367,26 +293,8 @@ export class ThemeToggle {
367
293
 
368
294
  return this;
369
295
  }
370
-
371
- /**
372
- * Render to another Jux component's container
373
- */
374
- renderTo(juxComponent: any): this {
375
- if (!juxComponent || typeof juxComponent !== 'object') {
376
- throw new Error('ThemeToggle.renderTo: Invalid component - not an object');
377
- }
378
-
379
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
380
- throw new Error('ThemeToggle.renderTo: Invalid component - missing _id (not a Jux component)');
381
- }
382
-
383
- return this.render(`#${juxComponent._id}`);
384
- }
385
296
  }
386
297
 
387
- /**
388
- * Factory helper
389
- */
390
298
  export function themeToggle(id: string, options: ThemeToggleOptions = {}): ThemeToggle {
391
299
  return new ThemeToggle(id, options);
392
300
  }