juxscript 1.0.19 → 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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +212 -165
- package/lib/components/badge.ts +93 -103
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +63 -122
- package/lib/components/card.ts +109 -155
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/charts/lib/chart-types.ts +159 -0
- package/lib/components/charts/lib/chart-utils.ts +160 -0
- package/lib/components/charts/lib/chart.ts +707 -0
- package/lib/components/checkbox.ts +264 -127
- package/lib/components/code.ts +75 -108
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +195 -147
- package/lib/components/dialog.ts +187 -157
- package/lib/components/divider.ts +85 -191
- package/lib/components/docs-data.json +544 -2027
- package/lib/components/dropdown.ts +178 -136
- package/lib/components/element.ts +227 -171
- package/lib/components/fileupload.ts +285 -228
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +46 -69
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +107 -95
- package/lib/components/icon.ts +160 -0
- package/lib/components/icons.ts +175 -0
- package/lib/components/include.ts +153 -5
- package/lib/components/input.ts +174 -374
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +378 -240
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +103 -97
- package/lib/components/modal.ts +138 -144
- package/lib/components/nav.ts +169 -90
- package/lib/components/paragraph.ts +49 -150
- package/lib/components/progress.ts +118 -200
- package/lib/components/radio.ts +297 -149
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +184 -186
- package/lib/components/sidebar.ts +152 -140
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +258 -188
- package/lib/components/table.ts +1117 -170
- package/lib/components/tabs.ts +162 -145
- package/lib/components/theme-toggle.ts +108 -169
- package/lib/components/tooltip.ts +86 -157
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +86 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -2
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1246
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1250
- package/lib/components/chart.ts +0 -127
- package/lib/components/doughnutchart.ts +0 -1191
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /package/lib/{themes → components/charts/lib}/charts.js +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { BaseComponent } from './BaseComponent.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base state interface for all form inputs
|
|
5
|
+
*/
|
|
6
|
+
export interface FormInputState extends Record<string, any> {
|
|
7
|
+
label: string;
|
|
8
|
+
required: boolean;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
name: string;
|
|
11
|
+
style: string;
|
|
12
|
+
class: string;
|
|
13
|
+
errorMessage?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Abstract base class for all form input components
|
|
18
|
+
* Extends BaseComponent with form-specific functionality
|
|
19
|
+
*/
|
|
20
|
+
export abstract class FormInput<TState extends FormInputState> extends BaseComponent<TState> {
|
|
21
|
+
protected _inputElement: HTMLElement | null = null;
|
|
22
|
+
protected _labelElement: HTMLLabelElement | null = null;
|
|
23
|
+
protected _errorElement: HTMLElement | null = null;
|
|
24
|
+
protected _onValidate?: (value: any) => boolean | string;
|
|
25
|
+
protected _hasBeenValidated: boolean = false; // NEW: Track if user has submitted/validated
|
|
26
|
+
|
|
27
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
28
|
+
* ABSTRACT METHODS (Child must implement)
|
|
29
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the current value of the input
|
|
33
|
+
*/
|
|
34
|
+
abstract getValue(): any;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the value of the input
|
|
38
|
+
*/
|
|
39
|
+
abstract setValue(value: any): this;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the actual input element (input, select, textarea, etc.)
|
|
43
|
+
*/
|
|
44
|
+
protected abstract _buildInputElement(): HTMLElement;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate the current value
|
|
48
|
+
*/
|
|
49
|
+
protected abstract _validateValue(value: any): boolean | string;
|
|
50
|
+
|
|
51
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
52
|
+
* COMMON FORM INPUT API
|
|
53
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
54
|
+
|
|
55
|
+
label(value: string): this {
|
|
56
|
+
this.state.label = value;
|
|
57
|
+
if (this._labelElement) {
|
|
58
|
+
this._labelElement.textContent = value;
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
required(value: boolean): this {
|
|
64
|
+
this.state.required = value;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
name(value: string): this {
|
|
69
|
+
this.state.name = value;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onValidate(handler: (value: any) => boolean | string): this {
|
|
74
|
+
this._onValidate = handler;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
79
|
+
* VALIDATION
|
|
80
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate the current value and show/hide errors
|
|
84
|
+
*/
|
|
85
|
+
validate(): boolean {
|
|
86
|
+
this._hasBeenValidated = true; // Mark as validated
|
|
87
|
+
const value = this.getValue();
|
|
88
|
+
const result = this._validateValue(value);
|
|
89
|
+
|
|
90
|
+
if (result === true) {
|
|
91
|
+
this._clearError();
|
|
92
|
+
return true;
|
|
93
|
+
} else {
|
|
94
|
+
this._showError(result as string);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if current value is valid without showing errors
|
|
101
|
+
*/
|
|
102
|
+
isValid(): boolean {
|
|
103
|
+
const value = this.getValue();
|
|
104
|
+
return this._validateValue(value) === true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Show error message
|
|
109
|
+
*/
|
|
110
|
+
protected _showError(message: string): void {
|
|
111
|
+
if (this._errorElement) {
|
|
112
|
+
this._errorElement.textContent = message;
|
|
113
|
+
this._errorElement.style.display = 'block';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this._inputElement) {
|
|
117
|
+
this._inputElement.classList.add('jux-input-invalid');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.state.errorMessage = message;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Clear error message
|
|
125
|
+
*/
|
|
126
|
+
protected _clearError(): void {
|
|
127
|
+
if (this._errorElement) {
|
|
128
|
+
this._errorElement.textContent = '';
|
|
129
|
+
this._errorElement.style.display = 'none';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this._inputElement) {
|
|
133
|
+
this._inputElement.classList.remove('jux-input-invalid');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.state.errorMessage = undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
140
|
+
* COMMON RENDER HELPERS
|
|
141
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build label element
|
|
145
|
+
*/
|
|
146
|
+
protected _renderLabel(): HTMLLabelElement {
|
|
147
|
+
const { label, required } = this.state;
|
|
148
|
+
|
|
149
|
+
const labelEl = document.createElement('label');
|
|
150
|
+
labelEl.className = 'jux-input-label';
|
|
151
|
+
labelEl.htmlFor = `${this._id}-input`;
|
|
152
|
+
labelEl.textContent = label;
|
|
153
|
+
|
|
154
|
+
if (required) {
|
|
155
|
+
const requiredSpan = document.createElement('span');
|
|
156
|
+
requiredSpan.className = 'jux-input-required';
|
|
157
|
+
requiredSpan.textContent = ' *';
|
|
158
|
+
labelEl.appendChild(requiredSpan);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this._labelElement = labelEl;
|
|
162
|
+
return labelEl;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build error element
|
|
167
|
+
*/
|
|
168
|
+
protected _renderError(): HTMLElement {
|
|
169
|
+
const errorEl = document.createElement('div');
|
|
170
|
+
errorEl.className = 'jux-input-error';
|
|
171
|
+
errorEl.id = `${this._id}-error`;
|
|
172
|
+
errorEl.style.display = 'none';
|
|
173
|
+
|
|
174
|
+
this._errorElement = errorEl;
|
|
175
|
+
return errorEl;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Wire up two-way sync for value property
|
|
180
|
+
*/
|
|
181
|
+
protected _wireFormSync(inputElement: HTMLElement, eventName: string = 'input'): void {
|
|
182
|
+
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
183
|
+
|
|
184
|
+
if (valueSync) {
|
|
185
|
+
const { stateObj, toState, toComponent } = valueSync;
|
|
186
|
+
|
|
187
|
+
// Default transforms
|
|
188
|
+
const transformToState = toState || ((v: any) => v);
|
|
189
|
+
const transformToComponent = toComponent || ((v: any) => v);
|
|
190
|
+
|
|
191
|
+
let isUpdating = false;
|
|
192
|
+
|
|
193
|
+
// State → Component
|
|
194
|
+
stateObj.subscribe((val: any) => {
|
|
195
|
+
if (isUpdating) return;
|
|
196
|
+
const transformed = transformToComponent(val);
|
|
197
|
+
this.setValue(transformed);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Component → State
|
|
201
|
+
inputElement.addEventListener(eventName, () => {
|
|
202
|
+
if (isUpdating) return;
|
|
203
|
+
isUpdating = true;
|
|
204
|
+
|
|
205
|
+
const value = this.getValue();
|
|
206
|
+
const transformed = transformToState(value);
|
|
207
|
+
this._clearError();
|
|
208
|
+
|
|
209
|
+
stateObj.set(transformed);
|
|
210
|
+
|
|
211
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
// Default behavior without sync
|
|
215
|
+
inputElement.addEventListener(eventName, () => {
|
|
216
|
+
this._clearError();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Only validate on blur IF the field has been validated before (e.g., after submit)
|
|
221
|
+
inputElement.addEventListener('blur', () => {
|
|
222
|
+
if (this._hasBeenValidated) {
|
|
223
|
+
this.validate();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Inject default form input styles
|
|
230
|
+
*/
|
|
231
|
+
protected _injectFormStyles(): void {
|
|
232
|
+
const styleId = 'jux-form-input-styles';
|
|
233
|
+
if (document.getElementById(styleId)) return;
|
|
234
|
+
|
|
235
|
+
const style = document.createElement('style');
|
|
236
|
+
style.id = styleId;
|
|
237
|
+
style.textContent = `
|
|
238
|
+
.jux-input {
|
|
239
|
+
margin-bottom: 16px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.jux-input-container {
|
|
243
|
+
position: relative;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.jux-input-with-icon .jux-input-element {
|
|
247
|
+
padding-left: 40px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.jux-input-icon {
|
|
251
|
+
position: absolute;
|
|
252
|
+
left: 12px;
|
|
253
|
+
top: 50%;
|
|
254
|
+
transform: translateY(-50%);
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
color: #6b7280;
|
|
259
|
+
pointer-events: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.jux-input-icon svg {
|
|
263
|
+
width: 18px;
|
|
264
|
+
height: 18px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.jux-input-label {
|
|
268
|
+
display: block;
|
|
269
|
+
margin-bottom: 6px;
|
|
270
|
+
font-weight: 500;
|
|
271
|
+
color: #374151;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.jux-input-required {
|
|
275
|
+
color: #ef4444;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.jux-input-element {
|
|
279
|
+
width: 100%;
|
|
280
|
+
padding: 8px 12px;
|
|
281
|
+
border: 1px solid #d1d5db;
|
|
282
|
+
border-radius: 6px;
|
|
283
|
+
font-size: 14px;
|
|
284
|
+
transition: border-color 0.2s;
|
|
285
|
+
box-sizing: border-box;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.jux-input-element:focus {
|
|
289
|
+
outline: none;
|
|
290
|
+
border-color: #3b82f6;
|
|
291
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.jux-input-element:disabled {
|
|
295
|
+
background-color: #f3f4f6;
|
|
296
|
+
cursor: not-allowed;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.jux-input-element.jux-input-invalid {
|
|
300
|
+
border-color: #ef4444;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.jux-input-element.jux-input-invalid:focus {
|
|
304
|
+
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.jux-input-error {
|
|
308
|
+
color: #ef4444;
|
|
309
|
+
font-size: 12px;
|
|
310
|
+
margin-top: 4px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.jux-input-counter {
|
|
314
|
+
text-align: right;
|
|
315
|
+
font-size: 12px;
|
|
316
|
+
color: #6b7280;
|
|
317
|
+
margin-top: 4px;
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
document.head.appendChild(style);
|
|
321
|
+
}
|
|
322
|
+
}
|
package/lib/components/button.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
+
import { renderIcon } from './icons.js';
|
|
3
|
+
|
|
4
|
+
// Event definitions
|
|
5
|
+
const TRIGGER_EVENTS = [] as const;
|
|
6
|
+
const CALLBACK_EVENTS = ['click'] as const; // ✅ Button fires click events
|
|
3
7
|
|
|
4
|
-
/**
|
|
5
|
-
* Button component options
|
|
6
|
-
*/
|
|
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;
|
|
@@ -19,13 +19,9 @@ export interface ButtonOptions {
|
|
|
19
19
|
class?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Button component state
|
|
24
|
-
*/
|
|
25
22
|
type ButtonState = {
|
|
26
23
|
label: string;
|
|
27
24
|
icon: string;
|
|
28
|
-
click: ((e: Event) => void) | null;
|
|
29
25
|
variant: string;
|
|
30
26
|
size: string;
|
|
31
27
|
disabled: boolean;
|
|
@@ -37,28 +33,15 @@ type ButtonState = {
|
|
|
37
33
|
class: string;
|
|
38
34
|
};
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
|
-
export class Button {
|
|
44
|
-
state: ButtonState;
|
|
45
|
-
container: HTMLElement | null = null;
|
|
46
|
-
_id: string;
|
|
47
|
-
id: string;
|
|
48
|
-
|
|
49
|
-
// Store bind() instructions
|
|
50
|
-
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
36
|
+
export class Button extends BaseComponent<ButtonState> {
|
|
37
|
+
private _buttonElement: HTMLButtonElement | null = null; // ✅ Store button reference
|
|
51
38
|
|
|
52
39
|
constructor(id: string, options?: ButtonOptions) {
|
|
53
|
-
this._id = id;
|
|
54
|
-
this.id = id;
|
|
55
|
-
|
|
56
40
|
const opts = options || {};
|
|
57
41
|
|
|
58
|
-
|
|
42
|
+
super(id, {
|
|
59
43
|
label: opts.label ?? 'Button',
|
|
60
44
|
icon: opts.icon ?? '',
|
|
61
|
-
click: opts.click ?? null,
|
|
62
45
|
variant: opts.variant ?? 'primary',
|
|
63
46
|
size: opts.size ?? 'medium',
|
|
64
47
|
disabled: opts.disabled ?? false,
|
|
@@ -68,12 +51,22 @@ export class Button {
|
|
|
68
51
|
type: opts.type ?? 'button',
|
|
69
52
|
style: opts.style ?? '',
|
|
70
53
|
class: opts.class ?? ''
|
|
71
|
-
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected getTriggerEvents(): readonly string[] {
|
|
58
|
+
return TRIGGER_EVENTS;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected getCallbackEvents(): readonly string[] {
|
|
62
|
+
return CALLBACK_EVENTS;
|
|
72
63
|
}
|
|
73
64
|
|
|
74
|
-
/*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
65
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
66
|
+
* FLUENT API
|
|
67
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
68
|
+
|
|
69
|
+
// ✅ Inherited from BaseComponent
|
|
77
70
|
|
|
78
71
|
label(value: string): this {
|
|
79
72
|
this.state.label = value;
|
|
@@ -85,20 +78,7 @@ export class Button {
|
|
|
85
78
|
return this;
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
|
|
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 {
|
|
81
|
+
variant(value: 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'link' | string): this {
|
|
102
82
|
this.state.variant = value;
|
|
103
83
|
return this;
|
|
104
84
|
}
|
|
@@ -108,16 +88,6 @@ export class Button {
|
|
|
108
88
|
return this;
|
|
109
89
|
}
|
|
110
90
|
|
|
111
|
-
disabled(value: boolean): this {
|
|
112
|
-
this.state.disabled = value;
|
|
113
|
-
return this;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
loading(value: boolean): this {
|
|
117
|
-
this.state.loading = value;
|
|
118
|
-
return this;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
91
|
iconPosition(value: 'left' | 'right'): this {
|
|
122
92
|
this.state.iconPosition = value;
|
|
123
93
|
return this;
|
|
@@ -133,102 +103,73 @@ export class Button {
|
|
|
133
103
|
return this;
|
|
134
104
|
}
|
|
135
105
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
106
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
107
|
+
* RENDER
|
|
108
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Override visible() to update the actual button element
|
|
112
|
+
*/
|
|
113
|
+
visible(value: boolean): this {
|
|
114
|
+
(this.state as any).visible = value;
|
|
115
|
+
|
|
116
|
+
// Update the button element directly, not the container
|
|
117
|
+
if (this._buttonElement) {
|
|
118
|
+
this._buttonElement.style.display = value ? '' : 'none';
|
|
119
|
+
}
|
|
140
120
|
|
|
141
|
-
class(value: string): this {
|
|
142
|
-
this.state.class = value;
|
|
143
121
|
return this;
|
|
144
122
|
}
|
|
145
123
|
|
|
146
|
-
/* -------------------------
|
|
147
|
-
* Render
|
|
148
|
-
* ------------------------- */
|
|
149
|
-
|
|
150
124
|
render(targetId?: string): this {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (targetId) {
|
|
154
|
-
const target = document.querySelector(targetId);
|
|
155
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
156
|
-
throw new Error(`Button: Target element "${targetId}" not found`);
|
|
157
|
-
}
|
|
158
|
-
container = target;
|
|
159
|
-
} else {
|
|
160
|
-
container = getOrCreateContainer(this._id);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
this.container = container;
|
|
164
|
-
const { label, icon, click, variant, size, disabled, loading, iconPosition, fullWidth, type, style, class: className } = this.state;
|
|
125
|
+
const container = this._setupContainer(targetId);
|
|
126
|
+
const { label: text, variant, size, disabled, icon, iconPosition, loading, style, class: className } = this.state;
|
|
165
127
|
|
|
166
128
|
const button = document.createElement('button');
|
|
129
|
+
this._buttonElement = button; // ✅ Store reference
|
|
167
130
|
button.className = `jux-button jux-button-${variant} jux-button-${size}`;
|
|
168
131
|
button.id = this._id;
|
|
169
|
-
button.type = type;
|
|
170
132
|
button.disabled = disabled || loading;
|
|
171
133
|
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (loading) {
|
|
177
|
-
button.classList.add('jux-button-loading');
|
|
178
|
-
}
|
|
134
|
+
if (className) button.className += ` ${className}`;
|
|
135
|
+
if (style) button.setAttribute('style', style);
|
|
179
136
|
|
|
180
|
-
if (className) {
|
|
181
|
-
button.className += ` ${className}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (style) {
|
|
185
|
-
button.setAttribute('style', style);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Build button content
|
|
189
137
|
if (icon && iconPosition === 'left') {
|
|
190
138
|
const iconEl = document.createElement('span');
|
|
191
|
-
iconEl.className = 'jux-button-icon
|
|
192
|
-
iconEl.
|
|
139
|
+
iconEl.className = 'jux-button-icon';
|
|
140
|
+
iconEl.appendChild(renderIcon(icon));
|
|
193
141
|
button.appendChild(iconEl);
|
|
194
142
|
}
|
|
195
143
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
button.appendChild(labelEl);
|
|
144
|
+
const textEl = document.createElement('span');
|
|
145
|
+
textEl.textContent = loading ? 'Loading...' : text;
|
|
146
|
+
button.appendChild(textEl);
|
|
200
147
|
|
|
201
148
|
if (icon && iconPosition === 'right') {
|
|
202
149
|
const iconEl = document.createElement('span');
|
|
203
|
-
iconEl.className = 'jux-button-icon
|
|
204
|
-
iconEl.
|
|
150
|
+
iconEl.className = 'jux-button-icon';
|
|
151
|
+
iconEl.appendChild(renderIcon(icon));
|
|
205
152
|
button.appendChild(iconEl);
|
|
206
153
|
}
|
|
207
154
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// Event binding - bind() method handlers
|
|
214
|
-
this._bindings.forEach(({ event, handler }) => {
|
|
215
|
-
button.addEventListener(event, handler as EventListener);
|
|
155
|
+
button.addEventListener('click', (e) => {
|
|
156
|
+
if (!disabled && !loading) {
|
|
157
|
+
this._triggerCallback('click', e);
|
|
158
|
+
}
|
|
216
159
|
});
|
|
217
160
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
161
|
+
this._wireStandardEvents(button);
|
|
162
|
+
this._wireAllSyncs();
|
|
221
163
|
|
|
222
|
-
|
|
223
|
-
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
224
|
-
throw new Error('Button.renderTo: Invalid component - not an object');
|
|
225
|
-
}
|
|
164
|
+
container.appendChild(button);
|
|
226
165
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
166
|
+
requestAnimationFrame(() => {
|
|
167
|
+
if ((window as any).lucide) {
|
|
168
|
+
(window as any).lucide.createIcons();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
230
171
|
|
|
231
|
-
return this
|
|
172
|
+
return this;
|
|
232
173
|
}
|
|
233
174
|
}
|
|
234
175
|
|