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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +143 -92
- package/lib/components/badge.ts +93 -94
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +40 -131
- package/lib/components/card.ts +57 -79
- 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/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
- package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
- package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
- package/lib/components/checkbox.ts +255 -204
- package/lib/components/code.ts +31 -78
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +180 -147
- package/lib/components/dialog.ts +218 -221
- package/lib/components/divider.ts +63 -87
- package/lib/components/docs-data.json +498 -2404
- package/lib/components/dropdown.ts +191 -236
- package/lib/components/element.ts +196 -145
- package/lib/components/fileupload.ts +253 -167
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +31 -97
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +51 -114
- package/lib/components/icon.ts +33 -120
- package/lib/components/icons.ts +2 -1
- package/lib/components/include.ts +76 -3
- package/lib/components/input.ts +155 -407
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +358 -261
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +63 -152
- package/lib/components/modal.ts +42 -129
- package/lib/components/nav.ts +79 -101
- package/lib/components/paragraph.ts +38 -102
- package/lib/components/progress.ts +108 -166
- package/lib/components/radio.ts +283 -234
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +189 -199
- package/lib/components/sidebar.ts +110 -141
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +254 -183
- package/lib/components/table.ts +1078 -208
- package/lib/components/tabs.ts +42 -106
- package/lib/components/theme-toggle.ts +73 -165
- package/lib/components/tooltip.ts +85 -316
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +67 -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 -1
- 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 -1128
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1322
- package/lib/components/doughnutchart.ts +0 -1259
- 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
package/lib/components/input.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
144
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
145
|
+
* FORM INPUT IMPLEMENTATION
|
|
146
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
193
147
|
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
221
|
-
|
|
160
|
+
getNumericValue(): number | null {
|
|
161
|
+
const num = Number(this.state.value);
|
|
162
|
+
return isNaN(num) ? null : num;
|
|
163
|
+
}
|
|
222
164
|
|
|
223
|
-
|
|
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
|
-
|
|
274
|
-
const
|
|
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
|
-
|
|
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 (
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
}
|
|
226
|
+
inputEl = document.createElement('input');
|
|
227
|
+
inputEl.type = type;
|
|
316
228
|
|
|
317
|
-
|
|
318
|
-
|
|
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 (
|
|
337
|
-
if (
|
|
338
|
-
if (
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
353
|
-
return this._validate(this.state.value) === true;
|
|
250
|
+
return inputEl;
|
|
354
251
|
}
|
|
355
252
|
|
|
356
|
-
/*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
253
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
254
|
+
* RENDER
|
|
255
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
359
256
|
|
|
360
257
|
render(targetId?: string): this {
|
|
361
|
-
|
|
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
|
|
260
|
+
const { icon, maxLength, type, style, class: className } = this.state;
|
|
381
261
|
|
|
382
|
-
//
|
|
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
|
-
|
|
391
|
-
|
|
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
|
-
|
|
412
|
-
iconEl.appendChild(iconElement);
|
|
283
|
+
iconEl.appendChild(renderIcon(icon));
|
|
413
284
|
inputContainer.appendChild(iconEl);
|
|
414
285
|
}
|
|
415
286
|
|
|
416
|
-
// Input
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
475
|
-
|
|
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
|
-
|
|
478
|
-
this.
|
|
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
|
-
//
|
|
483
|
-
inputEl.addEventListener('
|
|
484
|
-
|
|
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
|
|
488
|
-
this.
|
|
489
|
-
|
|
490
|
-
});
|
|
330
|
+
// Wire events
|
|
331
|
+
this._wireStandardEvents(wrapper);
|
|
332
|
+
this._wireFormSync(inputEl, 'input');
|
|
491
333
|
|
|
492
|
-
//
|
|
493
|
-
this._syncBindings.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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.
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
365
|
+
export function number(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
366
|
+
return new Input(id, { ...options, type: 'number' });
|
|
367
|
+
}
|
|
599
368
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
377
|
+
export function tel(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
378
|
+
return new Input(id, { ...options, type: 'tel' });
|
|
379
|
+
}
|
|
620
380
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
381
|
+
export function url(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
382
|
+
return new Input(id, { ...options, type: 'url' });
|
|
383
|
+
}
|
|
624
384
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
385
|
+
export function textarea(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
386
|
+
return new Input(id, { ...options, type: 'textarea' });
|
|
387
|
+
}
|
|
628
388
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
646
|
-
|
|
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
|
|
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
|
}
|