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,10 +1,10 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { FormInput, FormInputState } from './base/FormInput.js';
3
2
  import { renderIcon } from './icons.js';
4
3
 
5
- /**
6
- * Input component options
7
- */
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['change', 'input'] as const;
7
+
8
8
  export interface InputOptions {
9
9
  type?: string;
10
10
  value?: string;
@@ -26,18 +26,11 @@ export interface InputOptions {
26
26
  class?: string;
27
27
  }
28
28
 
29
- /**
30
- * Input component state
31
- */
32
- type InputState = {
29
+ interface InputState extends FormInputState {
33
30
  type: string;
34
31
  value: string;
35
32
  placeholder: string;
36
- label: string;
37
33
  icon: string;
38
- required: boolean;
39
- disabled: boolean;
40
- name: string;
41
34
  rows: number;
42
35
  min?: number;
43
36
  max?: number;
@@ -45,30 +38,11 @@ type InputState = {
45
38
  minLength?: number;
46
39
  maxLength?: number;
47
40
  pattern?: string;
48
- style: string;
49
- class: string;
50
- errorMessage?: string;
51
- };
52
-
53
- export class Input {
54
- state: InputState;
55
- container: HTMLElement | null = null;
56
- _id: string;
57
- id: string;
58
- private _onValidate?: (value: string) => boolean | string;
59
-
60
- // Store bind() instructions (DOM events only)
61
- private _bindings: Array<{ event: string, handler: Function }> = [];
62
-
63
- // Store sync() instructions (state synchronization)
64
- private _syncBindings: Array<{ property: string, stateObj: State<any>, toState?: Function, toComponent?: Function }> = [];
41
+ }
65
42
 
43
+ export class Input extends FormInput<InputState> {
66
44
  constructor(id: string, options: InputOptions = {}) {
67
- this._id = id;
68
- this.id = id;
69
- this._onValidate = options.onValidate;
70
-
71
- this.state = {
45
+ super(id, {
72
46
  type: options.type ?? 'text',
73
47
  value: options.value ?? '',
74
48
  placeholder: options.placeholder ?? '',
@@ -85,13 +59,33 @@ export class Input {
85
59
  maxLength: options.maxLength,
86
60
  pattern: options.pattern,
87
61
  style: options.style ?? '',
88
- class: options.class ?? ''
89
- };
62
+ class: options.class ?? '',
63
+ errorMessage: undefined
64
+ });
65
+
66
+ if (options.onValidate) {
67
+ this._onValidate = options.onValidate;
68
+ }
69
+ }
70
+
71
+ protected getTriggerEvents(): readonly string[] {
72
+ return TRIGGER_EVENTS;
90
73
  }
91
74
 
92
- /* -------------------------
93
- * Fluent API
94
- * ------------------------- */
75
+ protected getCallbackEvents(): readonly string[] {
76
+ return CALLBACK_EVENTS;
77
+ }
78
+
79
+ /* ═════════════════════════════════════════════════════════════════
80
+ * FLUENT API
81
+ * ═════════════════════════════════════════════════════════════════ */
82
+
83
+ // ✅ Inherited from FormInput/BaseComponent:
84
+ // - label(), required(), name(), onValidate()
85
+ // - validate(), isValid()
86
+ // - style(), class()
87
+ // - bind(), sync(), renderTo()
88
+ // - disabled(), enable(), disable()
95
89
 
96
90
  type(value: string): this {
97
91
  this.state.type = value;
@@ -99,9 +93,7 @@ export class Input {
99
93
  }
100
94
 
101
95
  value(value: string | number): this {
102
- this.state.value = String(value);
103
- this._updateElement();
104
- return this;
96
+ return this.setValue(String(value));
105
97
  }
106
98
 
107
99
  placeholder(value: string): this {
@@ -109,32 +101,11 @@ export class Input {
109
101
  return this;
110
102
  }
111
103
 
112
- label(value: string): this {
113
- this.state.label = value;
114
- return this;
115
- }
116
-
117
104
  icon(value: string): this {
118
105
  this.state.icon = value;
119
106
  return this;
120
107
  }
121
108
 
122
- required(value: boolean): this {
123
- this.state.required = value;
124
- return this;
125
- }
126
-
127
- disabled(value: boolean): this {
128
- this.state.disabled = value;
129
- this._updateElement();
130
- return this;
131
- }
132
-
133
- name(value: string): this {
134
- this.state.name = value;
135
- return this;
136
- }
137
-
138
109
  rows(value: number): this {
139
110
  this.state.rows = value;
140
111
  return this;
@@ -142,85 +113,56 @@ export class Input {
142
113
 
143
114
  min(value: number): this {
144
115
  this.state.min = value;
145
- this._updateElement();
146
116
  return this;
147
117
  }
148
118
 
149
119
  max(value: number): this {
150
120
  this.state.max = value;
151
- this._updateElement();
152
121
  return this;
153
122
  }
154
123
 
155
124
  step(value: number): this {
156
125
  this.state.step = value;
157
- this._updateElement();
158
126
  return this;
159
127
  }
160
128
 
161
129
  minLength(value: number): this {
162
130
  this.state.minLength = value;
163
- this._updateElement();
164
131
  return this;
165
132
  }
166
133
 
167
134
  maxLength(value: number): this {
168
135
  this.state.maxLength = value;
169
- this._updateElement();
170
136
  return this;
171
137
  }
172
138
 
173
139
  pattern(value: string): this {
174
140
  this.state.pattern = value;
175
- this._updateElement();
176
- return this;
177
- }
178
-
179
- style(value: string): this {
180
- this.state.style = value;
181
- return this;
182
- }
183
-
184
- class(value: string): this {
185
- this.state.class = value;
186
141
  return this;
187
142
  }
188
143
 
189
- onValidate(handler: (value: string) => boolean | string): this {
190
- this._onValidate = handler;
191
- return this;
192
- }
144
+ /* ═════════════════════════════════════════════════════════════════
145
+ * FORM INPUT IMPLEMENTATION
146
+ * ═════════════════════════════════════════════════════════════════ */
193
147
 
194
- /**
195
- * Bind event handler (stores for wiring in render)
196
- * DOM events only: input, change, blur, focus, etc.
197
- */
198
- bind(event: string, handler: Function): this {
199
- this._bindings.push({ event, handler });
200
- return this;
148
+ getValue(): string {
149
+ return this.state.value;
201
150
  }
202
151
 
203
- /**
204
- * Two-way sync with state (stores for wiring in render)
205
- *
206
- * @param property - Component property to sync ('value', 'label', etc)
207
- * @param stateObj - State object to sync with
208
- * @param toState - Optional transform function when going from component to state
209
- * @param toComponent - Optional transform function when going from state to component
210
- */
211
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
212
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
213
- throw new Error(`Input.sync: Expected a State object for property "${property}"`);
152
+ setValue(value: string): this {
153
+ this.state.value = value;
154
+ if (this._inputElement) {
155
+ (this._inputElement as HTMLInputElement | HTMLTextAreaElement).value = value;
214
156
  }
215
- this._syncBindings.push({ property, stateObj, toState, toComponent });
216
157
  return this;
217
158
  }
218
159
 
219
- /* -------------------------
220
- * Validation
221
- * ------------------------- */
160
+ getNumericValue(): number | null {
161
+ const num = Number(this.state.value);
162
+ return isNaN(num) ? null : num;
163
+ }
222
164
 
223
- private _validate(value: string): boolean | string {
165
+ protected _validateValue(value: string): boolean | string {
224
166
  const { required, type, min, max, minLength, maxLength, pattern } = this.state;
225
167
 
226
168
  if (required && !value.trim()) {
@@ -270,116 +212,54 @@ export class Input {
270
212
  return true;
271
213
  }
272
214
 
273
- private _showError(message: string): void {
274
- const errorEl = document.getElementById(`${this._id}-error`);
275
- const inputEl = document.getElementById(`${this._id}-input`);
276
-
277
- if (errorEl) {
278
- errorEl.textContent = message;
279
- errorEl.style.display = 'block';
280
- }
215
+ protected _buildInputElement(): HTMLElement {
216
+ const { type, value, placeholder, required, disabled, name, rows, min, max, step, minLength, maxLength, pattern } = this.state;
281
217
 
282
- if (inputEl) {
283
- inputEl.classList.add('jux-input-invalid');
284
- }
285
-
286
- this.state.errorMessage = message;
287
- }
288
-
289
- private _clearError(): void {
290
- const errorEl = document.getElementById(`${this._id}-error`);
291
- const inputEl = document.getElementById(`${this._id}-input`);
292
-
293
- if (errorEl) {
294
- errorEl.textContent = '';
295
- errorEl.style.display = 'none';
296
- }
297
-
298
- if (inputEl) {
299
- inputEl.classList.remove('jux-input-invalid');
300
- }
301
-
302
- this.state.errorMessage = undefined;
303
- }
304
-
305
- validate(): boolean {
306
- const result = this._validate(this.state.value);
218
+ let inputEl: HTMLInputElement | HTMLTextAreaElement;
307
219
 
308
- if (result === true) {
309
- this._clearError();
310
- return true;
220
+ if (type === 'textarea') {
221
+ inputEl = document.createElement('textarea');
222
+ inputEl.rows = rows;
223
+ if (minLength !== undefined) inputEl.minLength = minLength;
224
+ if (maxLength !== undefined) inputEl.maxLength = maxLength;
311
225
  } else {
312
- this._showError(result as string);
313
- return false;
314
- }
315
- }
226
+ inputEl = document.createElement('input');
227
+ inputEl.type = type;
316
228
 
317
- /* -------------------------
318
- * Helpers
319
- * ------------------------- */
320
-
321
- private _updateElement(): void {
322
- const input = document.getElementById(`${this._id}-input`) as HTMLInputElement | HTMLTextAreaElement;
323
- if (input) {
324
- input.value = this.state.value;
325
- input.disabled = this.state.disabled;
326
-
327
- if (input instanceof HTMLInputElement) {
328
- if (this.state.min !== undefined) input.min = String(this.state.min);
329
- if (this.state.max !== undefined) input.max = String(this.state.max);
330
- if (this.state.step !== undefined) input.step = String(this.state.step);
331
- if (this.state.minLength !== undefined) input.minLength = this.state.minLength;
332
- if (this.state.maxLength !== undefined) input.maxLength = this.state.maxLength;
333
- if (this.state.pattern) input.pattern = this.state.pattern;
229
+ if (type === 'number') {
230
+ if (min !== undefined) inputEl.min = String(min);
231
+ if (max !== undefined) inputEl.max = String(max);
232
+ if (step !== undefined) inputEl.step = String(step);
334
233
  }
335
234
 
336
- if (input instanceof HTMLTextAreaElement) {
337
- if (this.state.minLength !== undefined) input.minLength = this.state.minLength;
338
- if (this.state.maxLength !== undefined) input.maxLength = this.state.maxLength;
235
+ if (type === 'text' || type === 'email' || type === 'tel' || type === 'url') {
236
+ if (minLength !== undefined) inputEl.minLength = minLength;
237
+ if (maxLength !== undefined) inputEl.maxLength = maxLength;
238
+ if (pattern) inputEl.pattern = pattern;
339
239
  }
340
240
  }
341
- }
342
241
 
343
- getValue(): string {
344
- return this.state.value;
345
- }
346
-
347
- getNumericValue(): number | null {
348
- const num = Number(this.state.value);
349
- return isNaN(num) ? null : num;
350
- }
242
+ inputEl.className = 'jux-input-element';
243
+ inputEl.id = `${this._id}-input`;
244
+ inputEl.name = name;
245
+ inputEl.value = value;
246
+ inputEl.placeholder = placeholder;
247
+ inputEl.required = required;
248
+ inputEl.disabled = disabled;
351
249
 
352
- isValid(): boolean {
353
- return this._validate(this.state.value) === true;
250
+ return inputEl;
354
251
  }
355
252
 
356
- /* -------------------------
357
- * Render
358
- * ------------------------- */
253
+ /* ═════════════════════════════════════════════════════════════════
254
+ * RENDER
255
+ * ═════════════════════════════════════════════════════════════════ */
359
256
 
360
257
  render(targetId?: string): this {
361
- // === 1. SETUP: Get container ===
362
- let container: HTMLElement;
363
- if (targetId) {
364
- const target = document.querySelector(targetId);
365
- if (!target || !(target instanceof HTMLElement)) {
366
- throw new Error(`Input: Target element "${targetId}" not found`);
367
- }
368
- container = target;
369
- } else {
370
- container = getOrCreateContainer(this._id);
371
- }
372
- this.container = container;
373
-
374
- // === 2. PREPARE: Destructure state and check bindings ===
375
- const {
376
- type, value, placeholder, label, icon, required, disabled, name, rows,
377
- min, max, step, minLength, maxLength, pattern, style, class: className
378
- } = this.state;
258
+ const container = this._setupContainer(targetId);
379
259
 
380
- const hasValueSync = this._syncBindings.some(binding => binding.property === 'value');
260
+ const { icon, maxLength, type, style, class: className } = this.state;
381
261
 
382
- // === 3. BUILD: Create all DOM elements ===
262
+ // Build wrapper
383
263
  const wrapper = document.createElement('div');
384
264
  wrapper.className = 'jux-input';
385
265
  wrapper.id = this._id;
@@ -387,17 +267,9 @@ export class Input {
387
267
  if (style) wrapper.setAttribute('style', style);
388
268
 
389
269
  // Label
390
- const labelEl = document.createElement('label');
391
- labelEl.className = 'jux-input-label';
392
- labelEl.htmlFor = `${this._id}-input`;
393
- labelEl.textContent = label;
394
- if (required) {
395
- const requiredSpan = document.createElement('span');
396
- requiredSpan.className = 'jux-input-required';
397
- requiredSpan.textContent = ' *';
398
- labelEl.appendChild(requiredSpan);
270
+ if (this.state.label) {
271
+ wrapper.appendChild(this._renderLabel());
399
272
  }
400
- if (label) wrapper.appendChild(labelEl);
401
273
 
402
274
  // Input container
403
275
  const inputContainer = document.createElement('div');
@@ -408,139 +280,69 @@ export class Input {
408
280
  if (icon) {
409
281
  const iconEl = document.createElement('span');
410
282
  iconEl.className = 'jux-input-icon';
411
- const iconElement = renderIcon(icon);
412
- iconEl.appendChild(iconElement);
283
+ iconEl.appendChild(renderIcon(icon));
413
284
  inputContainer.appendChild(iconEl);
414
285
  }
415
286
 
416
- // Input/Textarea element
417
- let inputEl: HTMLInputElement | HTMLTextAreaElement;
418
- if (type === 'textarea') {
419
- inputEl = document.createElement('textarea');
420
- inputEl.rows = rows;
421
- if (minLength !== undefined) inputEl.minLength = minLength;
422
- if (maxLength !== undefined) inputEl.maxLength = maxLength;
423
- } else {
424
- inputEl = document.createElement('input');
425
- inputEl.type = type;
426
-
427
- if (type === 'number') {
428
- if (min !== undefined) inputEl.min = String(min);
429
- if (max !== undefined) inputEl.max = String(max);
430
- if (step !== undefined) inputEl.step = String(step);
431
- }
432
-
433
- if (type === 'text' || type === 'email' || type === 'tel' || type === 'url') {
434
- if (minLength !== undefined) inputEl.minLength = minLength;
435
- if (maxLength !== undefined) inputEl.maxLength = maxLength;
436
- if (pattern) inputEl.pattern = pattern;
437
- }
438
- }
439
-
440
- inputEl.className = 'jux-input-element';
441
- inputEl.id = `${this._id}-input`;
442
- inputEl.name = name;
443
- inputEl.value = value;
444
- inputEl.placeholder = placeholder;
445
- inputEl.required = required;
446
- inputEl.disabled = disabled;
447
-
287
+ // Input element
288
+ const inputEl = this._buildInputElement();
289
+ this._inputElement = inputEl;
448
290
  inputContainer.appendChild(inputEl);
449
291
  wrapper.appendChild(inputContainer);
450
292
 
451
293
  // Error element
452
- const errorEl = document.createElement('div');
453
- errorEl.className = 'jux-input-error';
454
- errorEl.id = `${this._id}-error`;
455
- errorEl.style.display = 'none';
456
- wrapper.appendChild(errorEl);
294
+ wrapper.appendChild(this._renderError());
457
295
 
458
296
  // Character counter
459
297
  if (maxLength && (type === 'text' || type === 'textarea')) {
460
298
  const counterEl = document.createElement('div');
461
299
  counterEl.className = 'jux-input-counter';
462
300
  counterEl.id = `${this._id}-counter`;
463
- counterEl.textContent = `${value.length}/${maxLength}`;
301
+ counterEl.textContent = `${this.state.value.length}/${maxLength}`;
464
302
  wrapper.appendChild(counterEl);
465
303
 
466
- // Wire counter immediately
467
304
  inputEl.addEventListener('input', () => {
468
- counterEl.textContent = `${inputEl.value.length}/${maxLength}`;
469
- });
470
- }
471
-
472
- // === 4. WIRE: Add event listeners ===
305
+ const input = inputEl as HTMLInputElement | HTMLTextAreaElement;
306
+ counterEl.textContent = `${input.value.length}/${maxLength}`;
307
+ this.state.value = input.value;
473
308
 
474
- // Default input handler (only if NOT using sync)
475
- if (!hasValueSync) {
309
+ // 🎯 Fire the input callback event
310
+ this._triggerCallback('input', input.value);
311
+ });
312
+ } else {
313
+ // Fire input event even without counter
476
314
  inputEl.addEventListener('input', () => {
477
- this.state.value = inputEl.value;
478
- this._clearError();
315
+ const input = inputEl as HTMLInputElement | HTMLTextAreaElement;
316
+ this.state.value = input.value;
317
+
318
+ // 🎯 Fire the input callback event
319
+ this._triggerCallback('input', input.value);
479
320
  });
480
321
  }
481
322
 
482
- // Always add blur validation
483
- inputEl.addEventListener('blur', () => {
484
- this.validate();
323
+ // Fire change event on blur
324
+ inputEl.addEventListener('change', () => {
325
+ const input = inputEl as HTMLInputElement | HTMLTextAreaElement;
326
+ // 🎯 Fire the change callback event
327
+ this._triggerCallback('change', input.value);
485
328
  });
486
329
 
487
- // Wire up custom event bindings (from .bind() calls)
488
- this._bindings.forEach(({ event, handler }) => {
489
- wrapper.addEventListener(event, handler as EventListener);
490
- });
330
+ // Wire events
331
+ this._wireStandardEvents(wrapper);
332
+ this._wireFormSync(inputEl, 'input');
491
333
 
492
- // Wire up sync bindings (from .sync() calls)
493
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
494
- if (property === 'value') {
495
- // Default transforms
496
- const transformToState = toState || ((v: string) => {
497
- return type === 'number' ? (parseInt(v) || 0) : v;
498
- });
499
- const transformToComponent = toComponent || ((v: any) => String(v));
500
-
501
- let isUpdating = false;
502
-
503
- // State → Input (when state changes, update input)
504
- stateObj.subscribe((val: any) => {
505
- if (isUpdating) return;
506
- const transformed = transformToComponent(val);
507
- if (inputEl.value !== transformed) {
508
- inputEl.value = transformed;
509
- this.state.value = transformed;
510
- }
511
- });
512
-
513
- // Input → State (when input changes, update state)
514
- inputEl.addEventListener('input', () => {
515
- if (isUpdating) return;
516
- isUpdating = true;
517
-
518
- const transformed = transformToState(inputEl.value);
519
- this.state.value = inputEl.value;
520
- this._clearError();
521
-
522
- stateObj.set(transformed);
523
-
524
- setTimeout(() => { isUpdating = false; }, 0);
525
- });
526
- }
527
- else if (property === 'label') {
528
- // Sync label (one-way: state → component)
529
- const transformToComponent = toComponent || ((v: any) => String(v));
530
-
531
- stateObj.subscribe((val: any) => {
532
- const transformed = transformToComponent(val);
533
- labelEl.textContent = transformed;
534
- this.state.label = transformed;
535
- });
536
- }
537
- });
334
+ // Sync label changes
335
+ const labelSync = this._syncBindings.find(b => b.property === 'label');
336
+ if (labelSync) {
337
+ const transform = labelSync.toComponent || ((v: any) => String(v));
338
+ labelSync.stateObj.subscribe((val: any) => {
339
+ this.label(transform(val));
340
+ });
341
+ }
538
342
 
539
- // === 5. RENDER: Append to DOM and finalize ===
540
343
  container.appendChild(wrapper);
541
- this._injectDefaultStyles();
344
+ this._injectFormStyles();
542
345
 
543
- // Trigger Lucide icon rendering
544
346
  requestAnimationFrame(() => {
545
347
  if ((window as any).lucide) {
546
348
  (window as any).lucide.createIcons();
@@ -549,107 +351,53 @@ export class Input {
549
351
 
550
352
  return this;
551
353
  }
354
+ }
552
355
 
553
- private _injectDefaultStyles(): void {
554
- const styleId = 'jux-input-styles';
555
- if (document.getElementById(styleId)) return;
556
-
557
- const style = document.createElement('style');
558
- style.id = styleId;
559
- style.textContent = `
560
- .jux-input {
561
- margin-bottom: 16px;
562
- }
563
-
564
- .jux-input-container {
565
- position: relative;
566
- }
567
-
568
- .jux-input-with-icon .jux-input-element {
569
- padding-left: 40px;
570
- }
571
-
572
- .jux-input-icon {
573
- position: absolute;
574
- left: 12px;
575
- top: 50%;
576
- transform: translateY(-50%);
577
- display: flex;
578
- align-items: center;
579
- justify-content: center;
580
- color: #6b7280;
581
- pointer-events: none;
582
- }
583
-
584
- .jux-input-icon svg {
585
- width: 18px;
586
- height: 18px;
587
- }
356
+ export function input(id: string, options: InputOptions = {}): Input {
357
+ return new Input(id, options);
358
+ }
588
359
 
589
- .jux-input-label {
590
- display: block;
591
- margin-bottom: 6px;
592
- font-weight: 500;
593
- color: #374151;
594
- }
360
+ // Shorter, consistent naming
361
+ export function text(id: string, options?: Omit<InputOptions, 'type'>): Input {
362
+ return new Input(id, { ...options, type: 'text' });
363
+ }
595
364
 
596
- .jux-input-required {
597
- color: #ef4444;
598
- }
365
+ export function number(id: string, options?: Omit<InputOptions, 'type'>): Input {
366
+ return new Input(id, { ...options, type: 'number' });
367
+ }
599
368
 
600
- .jux-input-element {
601
- width: 100%;
602
- padding: 8px 12px;
603
- border: 1px solid #d1d5db;
604
- border-radius: 6px;
605
- font-size: 14px;
606
- transition: border-color 0.2s;
607
- box-sizing: border-box;
608
- }
369
+ export function email(id: string, options?: Omit<InputOptions, 'type'>): Input {
370
+ return new Input(id, { ...options, type: 'email' });
371
+ }
609
372
 
610
- .jux-input-element:focus {
611
- outline: none;
612
- border-color: #3b82f6;
613
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
614
- }
373
+ export function password(id: string, options?: Omit<InputOptions, 'type'>): Input {
374
+ return new Input(id, { ...options, type: 'password' });
375
+ }
615
376
 
616
- .jux-input-element:disabled {
617
- background-color: #f3f4f6;
618
- cursor: not-allowed;
619
- }
377
+ export function tel(id: string, options?: Omit<InputOptions, 'type'>): Input {
378
+ return new Input(id, { ...options, type: 'tel' });
379
+ }
620
380
 
621
- .jux-input-element.jux-input-invalid {
622
- border-color: #ef4444;
623
- }
381
+ export function url(id: string, options?: Omit<InputOptions, 'type'>): Input {
382
+ return new Input(id, { ...options, type: 'url' });
383
+ }
624
384
 
625
- .jux-input-element.jux-input-invalid:focus {
626
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
627
- }
385
+ export function textarea(id: string, options?: Omit<InputOptions, 'type'>): Input {
386
+ return new Input(id, { ...options, type: 'textarea' });
387
+ }
628
388
 
629
- .jux-input-error {
630
- color: #ef4444;
631
- font-size: 12px;
632
- margin-top: 4px;
633
- }
389
+ export function range(id: string, options?: Omit<InputOptions, 'type'>): Input {
390
+ return new Input(id, { ...options, type: 'range' });
391
+ }
634
392
 
635
- .jux-input-counter {
636
- text-align: right;
637
- font-size: 12px;
638
- color: #6b7280;
639
- margin-top: 4px;
640
- }
641
- `;
642
- document.head.appendChild(style);
643
- }
393
+ export function date(id: string, options?: Omit<InputOptions, 'type'>): Input {
394
+ return new Input(id, { ...options, type: 'date' });
395
+ }
644
396
 
645
- renderTo(juxComponent: any): this {
646
- if (!juxComponent?._id) {
647
- throw new Error('Input.renderTo: Invalid component');
648
- }
649
- return this.render(`#${juxComponent._id}`);
650
- }
397
+ export function time(id: string, options?: Omit<InputOptions, 'type'>): Input {
398
+ return new Input(id, { ...options, type: 'time' });
651
399
  }
652
400
 
653
- export function input(id: string, options: InputOptions = {}): Input {
654
- return new Input(id, options);
401
+ export function color(id: string, options?: Omit<InputOptions, 'type'>): Input {
402
+ return new Input(id, { ...options, type: 'color' });
655
403
  }