juxscript 1.0.19 → 1.0.20

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 (43) hide show
  1. package/lib/components/alert.ts +124 -128
  2. package/lib/components/areachart.ts +169 -287
  3. package/lib/components/areachartsmooth.ts +2 -2
  4. package/lib/components/badge.ts +63 -72
  5. package/lib/components/barchart.ts +120 -48
  6. package/lib/components/button.ts +92 -60
  7. package/lib/components/card.ts +97 -121
  8. package/lib/components/chart-types.ts +159 -0
  9. package/lib/components/chart-utils.ts +160 -0
  10. package/lib/components/chart.ts +628 -48
  11. package/lib/components/checkbox.ts +137 -51
  12. package/lib/components/code.ts +89 -75
  13. package/lib/components/container.ts +1 -1
  14. package/lib/components/datepicker.ts +93 -78
  15. package/lib/components/dialog.ts +163 -130
  16. package/lib/components/divider.ts +111 -193
  17. package/lib/components/docs-data.json +697 -274
  18. package/lib/components/doughnutchart.ts +125 -57
  19. package/lib/components/dropdown.ts +172 -85
  20. package/lib/components/element.ts +66 -61
  21. package/lib/components/fileupload.ts +142 -171
  22. package/lib/components/heading.ts +64 -21
  23. package/lib/components/hero.ts +109 -34
  24. package/lib/components/icon.ts +247 -0
  25. package/lib/components/icons.ts +174 -0
  26. package/lib/components/include.ts +77 -2
  27. package/lib/components/input.ts +105 -53
  28. package/lib/components/list.ts +120 -79
  29. package/lib/components/menu.ts +97 -2
  30. package/lib/components/modal.ts +144 -63
  31. package/lib/components/nav.ts +153 -52
  32. package/lib/components/paragraph.ts +54 -91
  33. package/lib/components/progress.ts +83 -107
  34. package/lib/components/radio.ts +151 -52
  35. package/lib/components/select.ts +110 -102
  36. package/lib/components/sidebar.ts +148 -105
  37. package/lib/components/switch.ts +124 -125
  38. package/lib/components/table.ts +214 -137
  39. package/lib/components/tabs.ts +194 -113
  40. package/lib/components/theme-toggle.ts +38 -7
  41. package/lib/components/tooltip.ts +207 -47
  42. package/lib/jux.ts +24 -5
  43. package/package.json +1 -2
@@ -1,87 +1,64 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
2
3
 
3
- /**
4
- * Badge component options
5
- */
6
4
  export interface BadgeOptions {
7
5
  text?: string;
8
- variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
9
- size?: 'sm' | 'md' | 'lg';
10
- pill?: boolean;
6
+ variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
7
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
11
8
  style?: string;
12
9
  class?: string;
13
10
  }
14
11
 
15
- /**
16
- * Badge component state
17
- */
18
12
  type BadgeState = {
19
13
  text: string;
20
14
  variant: string;
21
15
  size: string;
22
- pill: boolean;
23
16
  style: string;
24
17
  class: string;
25
18
  };
26
19
 
27
- /**
28
- * Badge component - Status indicators, counts, labels
29
- *
30
- * Usage:
31
- * jux.badge('status', {
32
- * text: 'Active',
33
- * variant: 'success',
34
- * pill: true
35
- * }).render('#card');
36
- *
37
- * jux.badge('count', { text: '5' }).render('#notifications');
38
- */
39
20
  export class Badge {
40
21
  state: BadgeState;
41
22
  container: HTMLElement | null = null;
42
23
  _id: string;
43
24
  id: string;
44
25
 
26
+ private _bindings: Array<{ event: string, handler: Function }> = [];
27
+ private _syncBindings: Array<{
28
+ property: string,
29
+ stateObj: State<any>,
30
+ toState?: Function,
31
+ toComponent?: Function
32
+ }> = [];
33
+
45
34
  constructor(id: string, options: BadgeOptions = {}) {
46
35
  this._id = id;
47
36
  this.id = id;
48
37
 
49
38
  this.state = {
50
39
  text: options.text ?? '',
51
- variant: options.variant ?? 'default',
40
+ variant: options.variant ?? 'primary',
52
41
  size: options.size ?? 'md',
53
- pill: options.pill ?? false,
54
42
  style: options.style ?? '',
55
43
  class: options.class ?? ''
56
44
  };
57
45
  }
58
46
 
59
- /* -------------------------
60
- * Fluent API
61
- * ------------------------- */
62
-
63
47
  text(value: string): this {
64
48
  this.state.text = value;
65
- this._updateElement();
66
49
  return this;
67
50
  }
68
51
 
69
- variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
52
+ variant(value: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'): this {
70
53
  this.state.variant = value;
71
- this._updateElement();
72
54
  return this;
73
55
  }
74
56
 
75
- size(value: 'sm' | 'md' | 'lg'): this {
57
+ size(value: 'xs' | 'sm' | 'md' | 'lg' | 'xl'): this {
76
58
  this.state.size = value;
77
59
  return this;
78
60
  }
79
61
 
80
- pill(value: boolean): this {
81
- this.state.pill = value;
82
- return this;
83
- }
84
-
85
62
  style(value: string): this {
86
63
  this.state.style = value;
87
64
  return this;
@@ -92,61 +69,75 @@ export class Badge {
92
69
  return this;
93
70
  }
94
71
 
95
- /* -------------------------
96
- * Helpers
97
- * ------------------------- */
98
-
99
- private _updateElement(): void {
100
- const element = document.getElementById(this._id);
101
- if (element) {
102
- element.textContent = this.state.text;
103
- element.className = `jux-badge jux-badge-${this.state.variant} jux-badge-${this.state.size}`;
104
- if (this.state.pill) {
105
- element.classList.add('jux-badge-pill');
106
- }
107
- if (this.state.class) {
108
- element.className += ` ${this.state.class}`;
109
- }
110
- }
72
+ bind(event: string, handler: Function): this {
73
+ this._bindings.push({ event, handler });
74
+ return this;
111
75
  }
112
76
 
113
- /* -------------------------
114
- * Render
115
- * ------------------------- */
77
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
78
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
79
+ throw new Error(`Badge.sync: Expected a State object for property "${property}"`);
80
+ }
81
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
82
+ return this;
83
+ }
116
84
 
117
85
  render(targetId?: string): this {
86
+ // === 1. SETUP: Get or create container ===
118
87
  let container: HTMLElement;
119
-
120
88
  if (targetId) {
121
89
  const target = document.querySelector(targetId);
122
90
  if (!target || !(target instanceof HTMLElement)) {
123
- throw new Error(`Badge: Target element "${targetId}" not found`);
91
+ throw new Error(`Badge: Target "${targetId}" not found`);
124
92
  }
125
93
  container = target;
126
94
  } else {
127
95
  container = getOrCreateContainer(this._id);
128
96
  }
129
-
130
97
  this.container = container;
131
- const { text, variant, size, pill, style, class: className } = this.state;
132
98
 
99
+ // === 2. PREPARE: Destructure state ===
100
+ const { text, variant, size, style, class: className } = this.state;
101
+
102
+ // === 3. BUILD: Create DOM elements ===
133
103
  const badge = document.createElement('span');
134
104
  badge.className = `jux-badge jux-badge-${variant} jux-badge-${size}`;
135
105
  badge.id = this._id;
136
106
  badge.textContent = text;
107
+ if (className) badge.className += ` ${className}`;
108
+ if (style) badge.setAttribute('style', style);
109
+
110
+ // === 4. WIRE: Attach event listeners and sync bindings ===
111
+
112
+ // Wire custom bindings from .bind() calls
113
+ this._bindings.forEach(({ event, handler }) => {
114
+ badge.addEventListener(event, handler as EventListener);
115
+ });
116
+
117
+ // Wire sync bindings from .sync() calls
118
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
119
+ if (property === 'text') {
120
+ const transformToComponent = toComponent || ((v: any) => String(v));
121
+
122
+ stateObj.subscribe((val: any) => {
123
+ const transformed = transformToComponent(val);
124
+ badge.textContent = transformed;
125
+ this.state.text = transformed;
126
+ });
127
+ }
128
+ else if (property === 'variant') {
129
+ const transformToComponent = toComponent || ((v: any) => String(v));
130
+
131
+ stateObj.subscribe((val: any) => {
132
+ const transformed = transformToComponent(val);
133
+ badge.classList.remove(`jux-badge-${this.state.variant}`);
134
+ this.state.variant = transformed;
135
+ badge.classList.add(`jux-badge-${transformed}`);
136
+ });
137
+ }
138
+ });
137
139
 
138
- if (pill) {
139
- badge.classList.add('jux-badge-pill');
140
- }
141
-
142
- if (className) {
143
- badge.className += ` ${className}`;
144
- }
145
-
146
- if (style) {
147
- badge.setAttribute('style', style);
148
- }
149
-
140
+ // === 5. RENDER: Append to DOM and finalize ===
150
141
  container.appendChild(badge);
151
142
  return this;
152
143
  }
@@ -1,5 +1,24 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
2
  import { State } from '../reactivity/state.js';
3
+ import {
4
+ ChartDataPoint,
5
+ BarAreaChartOptions,
6
+ BarAreaChartState,
7
+ ChartTheme,
8
+ ChartStyleMode,
9
+ ChartOrientation,
10
+ ChartDirection,
11
+ LegendOrientation,
12
+ ChartPropertyMapping,
13
+ ChartStateObject
14
+ } from './chart-types.js';
15
+ import {
16
+ lightenColor,
17
+ getThemeConfig,
18
+ createLegend,
19
+ createDataTable,
20
+ applyThemeStyles
21
+ } from './chart-utils.js';
3
22
  import {
4
23
  googleTheme,
5
24
  seriesaTheme,
@@ -384,6 +403,105 @@ export class BarChart {
384
403
  return this;
385
404
  }
386
405
 
406
+ /* -------------------------
407
+ * Reactivity Support
408
+ * ------------------------- */
409
+
410
+ /**
411
+ * Sync a single property to a state object
412
+ */
413
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
414
+ const transform = toComponent || ((v: any) => v);
415
+
416
+ stateObj.subscribe((val: any) => {
417
+ const transformed = transform(val);
418
+
419
+ // Map property to correct method
420
+ switch (property) {
421
+ case 'data': this.data(transformed); break;
422
+ case 'title': this.title(transformed); break;
423
+ case 'subtitle': this.subtitle(transformed); break;
424
+ case 'width': this.width(transformed); break;
425
+ case 'height': this.height(transformed); break;
426
+ case 'theme': this.theme(transformed); break;
427
+ case 'styleMode': this.styleMode(transformed); break;
428
+ case 'borderRadius': this.borderRadius(transformed); break;
429
+ case 'showTicksX': this.showTicksX(transformed); break;
430
+ case 'showTicksY': this.showTicksY(transformed); break;
431
+ case 'showLegend': this.showLegend(transformed); break;
432
+ case 'showDataLabels': this.showDataLabels(transformed); break;
433
+ case 'showDataTable': this.showDataTable(transformed); break;
434
+ case 'animate': this.animate(transformed); break;
435
+ case 'animationDuration': this.animationDuration(transformed); break;
436
+ case 'legendOrientation': this.legendOrientation(transformed); break;
437
+ case 'chartOrientation': this.chartOrientation(transformed); break;
438
+ case 'chartDirection': this.chartDirection(transformed); break;
439
+ }
440
+ });
441
+
442
+ return this;
443
+ }
444
+
445
+ /**
446
+ * Sync multiple properties from a state object
447
+ */
448
+ syncState(stateObject: ChartStateObject, mapping?: ChartPropertyMapping): this {
449
+ // Default mapping: camelCase state names to method names
450
+ const defaultMapping: ChartPropertyMapping = {
451
+ chartType: 'type', // Not used in bar chart, ignored
452
+ chartTheme: 'theme',
453
+ chartStyleMode: 'styleMode',
454
+ borderRadius: 'borderRadius',
455
+ chartTitle: 'title',
456
+ chartWidth: 'width',
457
+ chartHeight: 'height',
458
+ showTicksX: 'showTicksX',
459
+ showTicksY: 'showTicksY',
460
+ showLegend: 'showLegend',
461
+ showDataTable: 'showDataTable',
462
+ showDataLabels: 'showDataLabels',
463
+ animate: 'animate',
464
+ animationDuration: 'animationDuration',
465
+ legendOrientation: 'legendOrientation',
466
+ chartOrientation: 'chartOrientation',
467
+ chartDirection: 'chartDirection'
468
+ };
469
+
470
+ const finalMapping = { ...defaultMapping, ...mapping };
471
+
472
+ // Iterate through state object and bind each property
473
+ Object.entries(stateObject).forEach(([key, stateObj]) => {
474
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
475
+ return;
476
+ }
477
+
478
+ const methodOrFunction = finalMapping[key];
479
+
480
+ if (!methodOrFunction) {
481
+ return;
482
+ }
483
+
484
+ if (typeof methodOrFunction === 'function') {
485
+ // Custom function mapping
486
+ stateObj.subscribe(methodOrFunction);
487
+ } else {
488
+ // String mapping to method name
489
+ const methodName = methodOrFunction as keyof this;
490
+ const method = this[methodName];
491
+
492
+ if (typeof method !== 'function') {
493
+ return;
494
+ }
495
+
496
+ stateObj.subscribe((val: any) => {
497
+ (method as Function).call(this, val);
498
+ });
499
+ }
500
+ });
501
+
502
+ return this;
503
+ }
504
+
387
505
  /* -------------------------
388
506
  * Update chart
389
507
  * ------------------------- */
@@ -1049,59 +1167,13 @@ export class BarChart {
1049
1167
  }
1050
1168
 
1051
1169
  private _applyTheme(themeName: string): void {
1052
- const themes: Record<string, any> = {
1053
- google: googleTheme,
1054
- seriesa: seriesaTheme,
1055
- hr: hrTheme,
1056
- figma: figmaTheme,
1057
- notion: notionTheme,
1058
- chalk: chalkTheme,
1059
- mint: mintTheme
1060
- };
1061
-
1062
- const theme = themes[themeName];
1170
+ const theme = getThemeConfig(themeName as ChartTheme);
1063
1171
  if (!theme) return;
1064
1172
 
1065
1173
  // Apply colors
1066
1174
  this.state.colors = theme.colors;
1067
1175
 
1068
- // Inject base styles (once)
1069
- const baseStyleId = 'jux-barchart-base-styles';
1070
- if (!document.getElementById(baseStyleId)) {
1071
- const style = document.createElement('style');
1072
- style.id = baseStyleId;
1073
- style.textContent = this._getBaseStyles();
1074
- document.head.appendChild(style);
1075
- }
1076
-
1077
- // Inject font (once per theme)
1078
- if (theme.font && !document.querySelector(`link[href="${theme.font}"]`)) {
1079
- const link = document.createElement('link');
1080
- link.rel = 'stylesheet';
1081
- link.href = theme.font;
1082
- document.head.appendChild(link);
1083
- }
1084
-
1085
- // Apply theme-specific styles
1086
- const styleId = `jux-barchart-theme-${themeName}`;
1087
- let styleElement = document.getElementById(styleId) as HTMLStyleElement;
1088
-
1089
- if (!styleElement) {
1090
- styleElement = document.createElement('style');
1091
- styleElement.id = styleId;
1092
- document.head.appendChild(styleElement);
1093
- }
1094
-
1095
- // Generate CSS with theme variables
1096
- const variablesCSS = Object.entries(theme.variables)
1097
- .map(([key, value]) => ` ${key}: ${value};`)
1098
- .join('\n');
1099
-
1100
- styleElement.textContent = `
1101
- .jux-barchart.theme-${themeName} {
1102
- ${variablesCSS}
1103
- }
1104
- `;
1176
+ applyThemeStyles(themeName as ChartTheme, 'jux-barchart', this._getBaseStyles());
1105
1177
  }
1106
1178
 
1107
1179
  private _applyThemeToWrapper(wrapper: HTMLElement): void {
@@ -1,5 +1,6 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
2
  import { State } from '../reactivity/state.js';
3
+ import { renderIcon } from './icons.js';
3
4
 
4
5
  /**
5
6
  * Button component options
@@ -7,7 +8,6 @@ import { State } from '../reactivity/state.js';
7
8
  export interface ButtonOptions {
8
9
  label?: string;
9
10
  icon?: string;
10
- click?: (e: Event) => void;
11
11
  variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'link' | string;
12
12
  size?: 'small' | 'medium' | 'large';
13
13
  disabled?: boolean;
@@ -25,7 +25,6 @@ export interface ButtonOptions {
25
25
  type ButtonState = {
26
26
  label: string;
27
27
  icon: string;
28
- click: ((e: Event) => void) | null;
29
28
  variant: string;
30
29
  size: string;
31
30
  disabled: boolean;
@@ -46,8 +45,14 @@ export class Button {
46
45
  _id: string;
47
46
  id: string;
48
47
 
49
- // Store bind() instructions
48
+ // CRITICAL: Store bind/sync instructions for deferred wiring
50
49
  private _bindings: Array<{ event: string, handler: Function }> = [];
50
+ private _syncBindings: Array<{
51
+ property: string,
52
+ stateObj: State<any>,
53
+ toState?: Function,
54
+ toComponent?: Function
55
+ }> = [];
51
56
 
52
57
  constructor(id: string, options?: ButtonOptions) {
53
58
  this._id = id;
@@ -58,7 +63,6 @@ export class Button {
58
63
  this.state = {
59
64
  label: opts.label ?? 'Button',
60
65
  icon: opts.icon ?? '',
61
- click: opts.click ?? null,
62
66
  variant: opts.variant ?? 'primary',
63
67
  size: opts.size ?? 'medium',
64
68
  disabled: opts.disabled ?? false,
@@ -85,20 +89,7 @@ export class Button {
85
89
  return this;
86
90
  }
87
91
 
88
- click(callback: (e: Event) => void): this {
89
- this.state.click = callback;
90
- return this;
91
- }
92
-
93
- /**
94
- * Bind event handler (stores for wiring in render)
95
- */
96
- bind(event: string, handler: Function): this {
97
- this._bindings.push({ event, handler });
98
- return this;
99
- }
100
-
101
- variant(value: string): this {
92
+ variant(value: 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'link' | string): this {
102
93
  this.state.variant = value;
103
94
  return this;
104
95
  }
@@ -143,91 +134,132 @@ export class Button {
143
134
  return this;
144
135
  }
145
136
 
137
+ /**
138
+ * Bind DOM events (click, hover, etc.)
139
+ * Stores handlers for wiring in render()
140
+ */
141
+ bind(event: string, handler: Function): this {
142
+ this._bindings.push({ event, handler });
143
+ return this;
144
+ }
145
+
146
+ /**
147
+ * Two-way state synchronization
148
+ * Stores sync instructions for wiring in render()
149
+ */
150
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
151
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
152
+ throw new Error(`Button.sync: Expected a State object for property "${property}"`);
153
+ }
154
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
155
+ return this;
156
+ }
157
+
146
158
  /* -------------------------
147
159
  * Render
148
160
  * ------------------------- */
149
161
 
150
162
  render(targetId?: string): this {
163
+ // === 1. SETUP: Get or create container ===
151
164
  let container: HTMLElement;
152
-
153
165
  if (targetId) {
154
166
  const target = document.querySelector(targetId);
155
167
  if (!target || !(target instanceof HTMLElement)) {
156
- throw new Error(`Button: Target element "${targetId}" not found`);
168
+ throw new Error(`Button: Target "${targetId}" not found`);
157
169
  }
158
170
  container = target;
159
171
  } else {
160
172
  container = getOrCreateContainer(this._id);
161
173
  }
162
-
163
174
  this.container = container;
164
- const { label, icon, click, variant, size, disabled, loading, iconPosition, fullWidth, type, style, class: className } = this.state;
165
175
 
176
+ // === 2. PREPARE: Destructure state ===
177
+ const { label: text, variant, size, disabled, icon, iconPosition, loading, style, class: className } = this.state;
178
+ const hasTextSync = this._syncBindings.some(b => b.property === 'text');
179
+ const hasDisabledSync = this._syncBindings.some(b => b.property === 'disabled');
180
+
181
+ // === 3. BUILD: Create DOM elements ===
166
182
  const button = document.createElement('button');
167
183
  button.className = `jux-button jux-button-${variant} jux-button-${size}`;
168
184
  button.id = this._id;
169
- button.type = type;
170
185
  button.disabled = disabled || loading;
171
186
 
172
- if (fullWidth) {
173
- button.classList.add('jux-button-full-width');
174
- }
175
-
176
- if (loading) {
177
- button.classList.add('jux-button-loading');
178
- }
179
-
180
- if (className) {
181
- button.className += ` ${className}`;
182
- }
183
-
184
- if (style) {
185
- button.setAttribute('style', style);
186
- }
187
+ if (className) button.className += ` ${className}`;
188
+ if (style) button.setAttribute('style', style);
187
189
 
188
- // Build button content
189
190
  if (icon && iconPosition === 'left') {
190
191
  const iconEl = document.createElement('span');
191
- iconEl.className = 'jux-button-icon jux-button-icon-left';
192
- iconEl.textContent = icon;
192
+ iconEl.className = 'jux-button-icon';
193
+ iconEl.appendChild(renderIcon(icon));
193
194
  button.appendChild(iconEl);
194
195
  }
195
196
 
196
- const labelEl = document.createElement('span');
197
- labelEl.className = 'jux-button-label';
198
- labelEl.textContent = loading ? 'Loading...' : label;
199
- button.appendChild(labelEl);
197
+ const textEl = document.createElement('span');
198
+ textEl.textContent = loading ? 'Loading...' : text;
199
+ button.appendChild(textEl);
200
200
 
201
201
  if (icon && iconPosition === 'right') {
202
202
  const iconEl = document.createElement('span');
203
- iconEl.className = 'jux-button-icon jux-button-icon-right';
204
- iconEl.textContent = icon;
203
+ iconEl.className = 'jux-button-icon';
204
+ iconEl.appendChild(renderIcon(icon));
205
205
  button.appendChild(iconEl);
206
206
  }
207
207
 
208
- // Event binding - legacy click handler from state
209
- if (click) {
210
- button.addEventListener('click', click);
211
- }
208
+ // === 4. WIRE: Attach event listeners and sync bindings ===
212
209
 
213
- // Event binding - bind() method handlers
210
+ // Wire custom bindings from .bind() calls
214
211
  this._bindings.forEach(({ event, handler }) => {
215
212
  button.addEventListener(event, handler as EventListener);
216
213
  });
217
214
 
215
+ // Wire sync bindings from .sync() calls
216
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
217
+ if (property === 'text') {
218
+ const transformToComponent = toComponent || ((v: any) => String(v));
219
+
220
+ stateObj.subscribe((val: any) => {
221
+ const transformed = transformToComponent(val);
222
+ textEl.textContent = this.state.loading ? 'Loading...' : transformed;
223
+ this.state.label = transformed;
224
+ });
225
+ }
226
+ else if (property === 'disabled') {
227
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
228
+
229
+ stateObj.subscribe((val: any) => {
230
+ const transformed = transformToComponent(val);
231
+ button.disabled = transformed || this.state.loading;
232
+ this.state.disabled = transformed;
233
+ });
234
+ }
235
+ else if (property === 'loading') {
236
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
237
+
238
+ stateObj.subscribe((val: any) => {
239
+ const transformed = transformToComponent(val);
240
+ button.disabled = this.state.disabled || transformed;
241
+ textEl.textContent = transformed ? 'Loading...' : this.state.label;
242
+ this.state.loading = transformed;
243
+ });
244
+ }
245
+ });
246
+
247
+ // === 5. RENDER: Append to DOM and finalize ===
218
248
  container.appendChild(button);
249
+
250
+ requestAnimationFrame(() => {
251
+ if ((window as any).lucide) {
252
+ (window as any).lucide.createIcons();
253
+ }
254
+ });
255
+
219
256
  return this;
220
257
  }
221
258
 
222
- renderTo(juxComponent: this): this {
223
- if (!juxComponent || typeof juxComponent !== 'object') {
224
- throw new Error('Button.renderTo: Invalid component - not an object');
259
+ renderTo(juxComponent: any): this {
260
+ if (!juxComponent?._id) {
261
+ throw new Error('Button.renderTo: Invalid component');
225
262
  }
226
-
227
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
228
- throw new Error('Button.renderTo: Invalid component - missing _id (not a Jux component)');
229
- }
230
-
231
263
  return this.render(`#${juxComponent._id}`);
232
264
  }
233
265
  }