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,94 +1,63 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+ import { renderIcon } from './icons.js';
2
4
 
3
- /**
4
- * Alert component options
5
- */
6
5
  export interface AlertOptions {
7
6
  message?: string;
8
- variant?: 'info' | 'success' | 'warning' | 'error';
9
- title?: string;
7
+ type?: 'info' | 'success' | 'warning' | 'error';
10
8
  dismissible?: boolean;
11
- onDismiss?: () => void;
12
9
  icon?: string;
13
10
  style?: string;
14
11
  class?: string;
15
12
  }
16
13
 
17
- /**
18
- * Alert component state
19
- */
20
14
  type AlertState = {
21
15
  message: string;
22
- variant: string;
23
- title: string;
16
+ type: string;
24
17
  dismissible: boolean;
25
18
  icon: string;
19
+ visible: boolean;
26
20
  style: string;
27
21
  class: string;
28
22
  };
29
23
 
30
- /**
31
- * Alert component - Status messages and notifications
32
- *
33
- * Usage:
34
- * jux.alert('my-alert', {
35
- * variant: 'success',
36
- * title: 'Success!',
37
- * message: 'Your changes have been saved.',
38
- * dismissible: true
39
- * }).render('#app');
40
- *
41
- * Variants: info, success, warning, error
42
- */
43
24
  export class Alert {
44
25
  state: AlertState;
45
26
  container: HTMLElement | null = null;
46
27
  _id: string;
47
28
  id: string;
48
- private _onDismiss?: () => void;
29
+
30
+ // CRITICAL: Store bind/sync instructions for deferred wiring
31
+ private _bindings: Array<{ event: string, handler: Function }> = [];
32
+ private _syncBindings: Array<{
33
+ property: string,
34
+ stateObj: State<any>,
35
+ toState?: Function,
36
+ toComponent?: Function
37
+ }> = [];
49
38
 
50
39
  constructor(id: string, options: AlertOptions = {}) {
51
40
  this._id = id;
52
41
  this.id = id;
53
- this._onDismiss = options.onDismiss;
54
-
55
- // Default icons per variant
56
- const defaultIcons = {
57
- info: 'ℹ️',
58
- success: '✅',
59
- warning: '⚠️',
60
- error: '❌'
61
- };
62
-
63
- const variant = options.variant ?? 'info';
64
42
 
65
43
  this.state = {
66
44
  message: options.message ?? '',
67
- variant,
68
- title: options.title ?? '',
69
- dismissible: options.dismissible ?? false,
70
- icon: options.icon ?? defaultIcons[variant as keyof typeof defaultIcons],
45
+ type: options.type ?? 'info',
46
+ dismissible: options.dismissible ?? true,
47
+ icon: options.icon ?? '',
48
+ visible: true,
71
49
  style: options.style ?? '',
72
50
  class: options.class ?? ''
73
51
  };
74
52
  }
75
53
 
76
- /* -------------------------
77
- * Fluent API
78
- * ------------------------- */
79
-
80
54
  message(value: string): this {
81
55
  this.state.message = value;
82
56
  return this;
83
57
  }
84
58
 
85
- variant(value: 'info' | 'success' | 'warning' | 'error'): this {
86
- this.state.variant = value;
87
- return this;
88
- }
89
-
90
- title(value: string): this {
91
- this.state.title = value;
59
+ type(value: 'info' | 'success' | 'warning' | 'error'): this {
60
+ this.state.type = value;
92
61
  return this;
93
62
  }
94
63
 
@@ -112,129 +81,156 @@ export class Alert {
112
81
  return this;
113
82
  }
114
83
 
115
- /* -------------------------
116
- * Methods
117
- * ------------------------- */
118
-
119
- /**
120
- * Dismiss/remove the alert
121
- */
122
- dismiss(): void {
123
- const element = document.getElementById(this._id);
124
- if (element) {
125
- element.style.opacity = '0';
126
- element.style.transform = 'translateY(-10px)';
127
- setTimeout(() => {
128
- element.remove();
129
- if (this._onDismiss) {
130
- this._onDismiss();
131
- }
132
- }, 200);
84
+ bind(event: string, handler: Function): this {
85
+ this._bindings.push({ event, handler });
86
+ return this;
87
+ }
88
+
89
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
90
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
91
+ throw new Error(`Alert.sync: Expected a State object for property "${property}"`);
133
92
  }
93
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
94
+ return this;
134
95
  }
135
96
 
136
- /**
137
- * Auto-dismiss after delay
138
- */
139
- autoDismiss(delay: number = 5000): this {
140
- setTimeout(() => this.dismiss(), delay);
97
+ show(): this {
98
+ this.state.visible = true;
99
+ const alertEl = document.getElementById(this._id);
100
+ if (alertEl) {
101
+ alertEl.style.display = 'flex';
102
+ }
141
103
  return this;
142
104
  }
143
105
 
144
- /* -------------------------
145
- * Render
146
- * ------------------------- */
106
+ hide(): this {
107
+ this.state.visible = false;
108
+ const alertEl = document.getElementById(this._id);
109
+ if (alertEl) {
110
+ alertEl.style.display = 'none';
111
+ }
112
+ return this;
113
+ }
147
114
 
148
115
  render(targetId?: string): this {
116
+ // === 1. SETUP: Get or create container ===
149
117
  let container: HTMLElement;
150
-
151
118
  if (targetId) {
152
119
  const target = document.querySelector(targetId);
153
120
  if (!target || !(target instanceof HTMLElement)) {
154
- throw new Error(`Alert: Target element "${targetId}" not found`);
121
+ throw new Error(`Alert: Target "${targetId}" not found`);
155
122
  }
156
123
  container = target;
157
124
  } else {
158
125
  container = getOrCreateContainer(this._id);
159
126
  }
160
-
161
127
  this.container = container;
162
- const { message, variant, title, dismissible, icon, style, class: className } = this.state;
163
128
 
129
+ // === 2. PREPARE: Destructure state and check sync flags ===
130
+ const { message, type, dismissible, icon, style, class: className } = this.state;
131
+ const hasVisibleSync = this._syncBindings.some(b => b.property === 'visible');
132
+
133
+ // === 3. BUILD: Create DOM elements ===
164
134
  const alert = document.createElement('div');
165
- alert.className = `jux-alert jux-alert-${variant}`;
135
+ alert.className = `jux-alert jux-alert-${type}`;
166
136
  alert.id = this._id;
167
- alert.setAttribute('role', 'alert');
137
+ if (className) alert.className += ` ${className}`;
138
+ if (style) alert.setAttribute('style', style);
168
139
 
169
- if (className) {
170
- alert.className += ` ${className}`;
171
- }
172
-
173
- if (style) {
174
- alert.setAttribute('style', style);
175
- }
176
-
177
- // Icon
178
140
  if (icon) {
179
141
  const iconEl = document.createElement('span');
180
142
  iconEl.className = 'jux-alert-icon';
181
- iconEl.textContent = icon;
143
+ iconEl.appendChild(renderIcon(icon));
182
144
  alert.appendChild(iconEl);
183
145
  }
184
146
 
185
- // Content
186
147
  const content = document.createElement('div');
187
148
  content.className = 'jux-alert-content';
149
+ content.textContent = message;
150
+ alert.appendChild(content);
188
151
 
189
- if (title) {
190
- const titleEl = document.createElement('div');
191
- titleEl.className = 'jux-alert-title';
192
- titleEl.textContent = title;
193
- content.appendChild(titleEl);
194
- }
195
-
196
- if (message) {
197
- const messageEl = document.createElement('div');
198
- messageEl.className = 'jux-alert-message';
199
- messageEl.textContent = message;
200
- content.appendChild(messageEl);
152
+ if (dismissible) {
153
+ const closeBtn = document.createElement('button');
154
+ closeBtn.className = 'jux-alert-close';
155
+ closeBtn.innerHTML = '×';
156
+ alert.appendChild(closeBtn);
201
157
  }
202
158
 
203
- alert.appendChild(content);
159
+ // === 4. WIRE: Attach event listeners and sync bindings ===
204
160
 
205
- // Dismiss button
206
- if (dismissible) {
207
- const dismissBtn = document.createElement('button');
208
- dismissBtn.className = 'jux-alert-dismiss';
209
- dismissBtn.innerHTML = '×';
210
- dismissBtn.setAttribute('aria-label', 'Dismiss alert');
211
- dismissBtn.addEventListener('click', () => this.dismiss());
212
- alert.appendChild(dismissBtn);
161
+ // Default dismiss behavior (only if NOT using sync)
162
+ if (!hasVisibleSync && dismissible) {
163
+ const closeBtn = alert.querySelector('.jux-alert-close');
164
+ closeBtn?.addEventListener('click', () => {
165
+ alert.remove();
166
+ });
213
167
  }
214
168
 
169
+ // Wire custom bindings from .bind() calls
170
+ this._bindings.forEach(({ event, handler }) => {
171
+ alert.addEventListener(event, handler as EventListener);
172
+ });
173
+
174
+ // Wire sync bindings from .sync() calls
175
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
176
+ if (property === 'message') {
177
+ const transformToComponent = toComponent || ((v: any) => String(v));
178
+
179
+ stateObj.subscribe((val: any) => {
180
+ const transformed = transformToComponent(val);
181
+ content.textContent = transformed;
182
+ this.state.message = transformed;
183
+ });
184
+ }
185
+ else if (property === 'visible') {
186
+ const transformToState = toState || ((v: any) => Boolean(v));
187
+ const transformToComponent = toComponent || ((v: any) => Boolean(v));
188
+
189
+ let isUpdating = false;
190
+
191
+ // State → Component
192
+ stateObj.subscribe((val: any) => {
193
+ if (isUpdating) return;
194
+ const transformed = transformToComponent(val);
195
+ alert.style.display = transformed ? 'flex' : 'none';
196
+ });
197
+
198
+ // Component → State (close button)
199
+ if (dismissible) {
200
+ const closeBtn = alert.querySelector('.jux-alert-close');
201
+ closeBtn?.addEventListener('click', () => {
202
+ if (isUpdating) return;
203
+ isUpdating = true;
204
+
205
+ alert.style.display = 'none';
206
+ stateObj.set(transformToState(false));
207
+
208
+ setTimeout(() => { isUpdating = false; }, 0);
209
+ });
210
+ }
211
+ }
212
+ });
213
+
214
+ // === 5. RENDER: Append to DOM and finalize ===
215
215
  container.appendChild(alert);
216
+
217
+ requestAnimationFrame(() => {
218
+ if ((window as any).lucide) {
219
+ (window as any).lucide.createIcons();
220
+ }
221
+ });
222
+
216
223
  return this;
217
224
  }
218
225
 
219
- /**
220
- * Render to another Jux component's container
221
- */
222
226
  renderTo(juxComponent: any): this {
223
- if (!juxComponent || typeof juxComponent !== 'object') {
224
- throw new Error('Alert.renderTo: Invalid component - not an object');
225
- }
226
-
227
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
228
- throw new Error('Alert.renderTo: Invalid component - missing _id (not a Jux component)');
227
+ if (!juxComponent?._id) {
228
+ throw new Error('Alert.renderTo: Invalid component');
229
229
  }
230
-
231
230
  return this.render(`#${juxComponent._id}`);
232
231
  }
233
232
  }
234
233
 
235
- /**
236
- * Factory helper
237
- */
238
234
  export function alert(id: string, options: AlertOptions = {}): Alert {
239
235
  return new Alert(id, options);
240
236
  }