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
package/lib/components/input.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { FormInput, FormInputState } from './base/FormInput.js';
|
|
2
|
+
import { renderIcon } from './icons.js';
|
|
3
|
+
|
|
4
|
+
// Event definitions
|
|
5
|
+
const TRIGGER_EVENTS = [] as const;
|
|
6
|
+
const CALLBACK_EVENTS = ['change', 'input'] as const;
|
|
3
7
|
|
|
4
|
-
/**
|
|
5
|
-
* Input component options
|
|
6
|
-
*/
|
|
7
8
|
export interface InputOptions {
|
|
8
9
|
type?: string;
|
|
9
10
|
value?: string;
|
|
10
11
|
placeholder?: string;
|
|
11
12
|
label?: string;
|
|
13
|
+
icon?: string;
|
|
12
14
|
required?: boolean;
|
|
13
15
|
disabled?: boolean;
|
|
14
16
|
name?: string;
|
|
@@ -19,23 +21,16 @@ export interface InputOptions {
|
|
|
19
21
|
minLength?: number;
|
|
20
22
|
maxLength?: number;
|
|
21
23
|
pattern?: string;
|
|
22
|
-
onChange?: (value: string) => void;
|
|
23
24
|
onValidate?: (value: string) => boolean | string;
|
|
24
25
|
style?: string;
|
|
25
26
|
class?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
* Input component state
|
|
30
|
-
*/
|
|
31
|
-
type InputState = {
|
|
29
|
+
interface InputState extends FormInputState {
|
|
32
30
|
type: string;
|
|
33
31
|
value: string;
|
|
34
32
|
placeholder: string;
|
|
35
|
-
|
|
36
|
-
required: boolean;
|
|
37
|
-
disabled: boolean;
|
|
38
|
-
name: string;
|
|
33
|
+
icon: string;
|
|
39
34
|
rows: number;
|
|
40
35
|
min?: number;
|
|
41
36
|
max?: number;
|
|
@@ -43,39 +38,16 @@ type InputState = {
|
|
|
43
38
|
minLength?: number;
|
|
44
39
|
maxLength?: number;
|
|
45
40
|
pattern?: string;
|
|
46
|
-
|
|
47
|
-
class: string;
|
|
48
|
-
errorMessage?: string;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Input component
|
|
53
|
-
*/
|
|
54
|
-
export class Input {
|
|
55
|
-
state: InputState;
|
|
56
|
-
container: HTMLElement | null = null;
|
|
57
|
-
_id: string;
|
|
58
|
-
id: string;
|
|
59
|
-
private _onChange?: (value: string) => void;
|
|
60
|
-
private _onValidate?: (value: string) => boolean | string;
|
|
61
|
-
|
|
62
|
-
// Store bind() instructions
|
|
63
|
-
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
64
|
-
|
|
65
|
-
// Store sync() instructions
|
|
66
|
-
private _syncBindings: Array<{ property: string, stateObj: State<any>, toState?: Function, toComponent?: Function }> = [];
|
|
41
|
+
}
|
|
67
42
|
|
|
43
|
+
export class Input extends FormInput<InputState> {
|
|
68
44
|
constructor(id: string, options: InputOptions = {}) {
|
|
69
|
-
|
|
70
|
-
this.id = id;
|
|
71
|
-
this._onChange = options.onChange;
|
|
72
|
-
this._onValidate = options.onValidate;
|
|
73
|
-
|
|
74
|
-
this.state = {
|
|
45
|
+
super(id, {
|
|
75
46
|
type: options.type ?? 'text',
|
|
76
47
|
value: options.value ?? '',
|
|
77
48
|
placeholder: options.placeholder ?? '',
|
|
78
49
|
label: options.label ?? '',
|
|
50
|
+
icon: options.icon ?? '',
|
|
79
51
|
required: options.required ?? false,
|
|
80
52
|
disabled: options.disabled ?? false,
|
|
81
53
|
name: options.name ?? id,
|
|
@@ -87,13 +59,33 @@ export class Input {
|
|
|
87
59
|
maxLength: options.maxLength,
|
|
88
60
|
pattern: options.pattern,
|
|
89
61
|
style: options.style ?? '',
|
|
90
|
-
class: options.class ?? ''
|
|
91
|
-
|
|
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;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected getCallbackEvents(): readonly string[] {
|
|
76
|
+
return CALLBACK_EVENTS;
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
/*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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()
|
|
97
89
|
|
|
98
90
|
type(value: string): this {
|
|
99
91
|
this.state.type = value;
|
|
@@ -101,9 +93,7 @@ export class Input {
|
|
|
101
93
|
}
|
|
102
94
|
|
|
103
95
|
value(value: string | number): this {
|
|
104
|
-
this.
|
|
105
|
-
this._updateElement();
|
|
106
|
-
return this;
|
|
96
|
+
return this.setValue(String(value));
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
placeholder(value: string): this {
|
|
@@ -111,24 +101,8 @@ export class Input {
|
|
|
111
101
|
return this;
|
|
112
102
|
}
|
|
113
103
|
|
|
114
|
-
|
|
115
|
-
this.state.
|
|
116
|
-
return this;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
required(value: boolean): this {
|
|
120
|
-
this.state.required = value;
|
|
121
|
-
return this;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
disabled(value: boolean): this {
|
|
125
|
-
this.state.disabled = value;
|
|
126
|
-
this._updateElement();
|
|
127
|
-
return this;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
name(value: string): this {
|
|
131
|
-
this.state.name = value;
|
|
104
|
+
icon(value: string): this {
|
|
105
|
+
this.state.icon = value;
|
|
132
106
|
return this;
|
|
133
107
|
}
|
|
134
108
|
|
|
@@ -139,86 +113,56 @@ export class Input {
|
|
|
139
113
|
|
|
140
114
|
min(value: number): this {
|
|
141
115
|
this.state.min = value;
|
|
142
|
-
this._updateElement();
|
|
143
116
|
return this;
|
|
144
117
|
}
|
|
145
118
|
|
|
146
119
|
max(value: number): this {
|
|
147
120
|
this.state.max = value;
|
|
148
|
-
this._updateElement();
|
|
149
121
|
return this;
|
|
150
122
|
}
|
|
151
123
|
|
|
152
124
|
step(value: number): this {
|
|
153
125
|
this.state.step = value;
|
|
154
|
-
this._updateElement();
|
|
155
126
|
return this;
|
|
156
127
|
}
|
|
157
128
|
|
|
158
129
|
minLength(value: number): this {
|
|
159
130
|
this.state.minLength = value;
|
|
160
|
-
this._updateElement();
|
|
161
131
|
return this;
|
|
162
132
|
}
|
|
163
133
|
|
|
164
134
|
maxLength(value: number): this {
|
|
165
135
|
this.state.maxLength = value;
|
|
166
|
-
this._updateElement();
|
|
167
136
|
return this;
|
|
168
137
|
}
|
|
169
138
|
|
|
170
139
|
pattern(value: string): this {
|
|
171
140
|
this.state.pattern = value;
|
|
172
|
-
this._updateElement();
|
|
173
|
-
return this;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
style(value: string): this {
|
|
177
|
-
this.state.style = value;
|
|
178
141
|
return this;
|
|
179
142
|
}
|
|
180
143
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
onChange(handler: (value: string) => void): this {
|
|
187
|
-
this._onChange = handler;
|
|
188
|
-
return this;
|
|
189
|
-
}
|
|
144
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
145
|
+
* FORM INPUT IMPLEMENTATION
|
|
146
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
190
147
|
|
|
191
|
-
|
|
192
|
-
this.
|
|
193
|
-
return this;
|
|
148
|
+
getValue(): string {
|
|
149
|
+
return this.state.value;
|
|
194
150
|
}
|
|
195
151
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
152
|
+
setValue(value: string): this {
|
|
153
|
+
this.state.value = value;
|
|
154
|
+
if (this._inputElement) {
|
|
155
|
+
(this._inputElement as HTMLInputElement | HTMLTextAreaElement).value = value;
|
|
156
|
+
}
|
|
201
157
|
return this;
|
|
202
158
|
}
|
|
203
159
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* @param property - Component property to sync ('value', 'label', etc)
|
|
208
|
-
* @param stateObj - State object to sync with
|
|
209
|
-
* @param toState - Optional transform function when going from component to state
|
|
210
|
-
* @param toComponent - Optional transform function when going from state to component
|
|
211
|
-
*/
|
|
212
|
-
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
213
|
-
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
214
|
-
return this;
|
|
160
|
+
getNumericValue(): number | null {
|
|
161
|
+
const num = Number(this.state.value);
|
|
162
|
+
return isNaN(num) ? null : num;
|
|
215
163
|
}
|
|
216
164
|
|
|
217
|
-
|
|
218
|
-
* Validation
|
|
219
|
-
* ------------------------- */
|
|
220
|
-
|
|
221
|
-
private _validate(value: string): boolean | string {
|
|
165
|
+
protected _validateValue(value: string): boolean | string {
|
|
222
166
|
const { required, type, min, max, minLength, maxLength, pattern } = this.state;
|
|
223
167
|
|
|
224
168
|
if (required && !value.trim()) {
|
|
@@ -268,137 +212,9 @@ export class Input {
|
|
|
268
212
|
return true;
|
|
269
213
|
}
|
|
270
214
|
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
const inputEl = document.getElementById(`${this._id}-input`);
|
|
274
|
-
|
|
275
|
-
if (errorEl) {
|
|
276
|
-
errorEl.textContent = message;
|
|
277
|
-
errorEl.style.display = 'block';
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (inputEl) {
|
|
281
|
-
inputEl.classList.add('jux-input-invalid');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.state.errorMessage = message;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private _clearError(): void {
|
|
288
|
-
const errorEl = document.getElementById(`${this._id}-error`);
|
|
289
|
-
const inputEl = document.getElementById(`${this._id}-input`);
|
|
290
|
-
|
|
291
|
-
if (errorEl) {
|
|
292
|
-
errorEl.textContent = '';
|
|
293
|
-
errorEl.style.display = 'none';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (inputEl) {
|
|
297
|
-
inputEl.classList.remove('jux-input-invalid');
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
this.state.errorMessage = undefined;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
validate(): boolean {
|
|
304
|
-
const result = this._validate(this.state.value);
|
|
305
|
-
|
|
306
|
-
if (result === true) {
|
|
307
|
-
this._clearError();
|
|
308
|
-
return true;
|
|
309
|
-
} else {
|
|
310
|
-
this._showError(result as string);
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/* -------------------------
|
|
316
|
-
* Helpers
|
|
317
|
-
* ------------------------- */
|
|
318
|
-
|
|
319
|
-
private _updateElement(): void {
|
|
320
|
-
const input = document.getElementById(`${this._id}-input`) as HTMLInputElement | HTMLTextAreaElement;
|
|
321
|
-
if (input) {
|
|
322
|
-
input.value = this.state.value;
|
|
323
|
-
input.disabled = this.state.disabled;
|
|
324
|
-
|
|
325
|
-
if (input instanceof HTMLInputElement) {
|
|
326
|
-
if (this.state.min !== undefined) input.min = String(this.state.min);
|
|
327
|
-
if (this.state.max !== undefined) input.max = String(this.state.max);
|
|
328
|
-
if (this.state.step !== undefined) input.step = String(this.state.step);
|
|
329
|
-
if (this.state.minLength !== undefined) input.minLength = this.state.minLength;
|
|
330
|
-
if (this.state.maxLength !== undefined) input.maxLength = this.state.maxLength;
|
|
331
|
-
if (this.state.pattern) input.pattern = this.state.pattern;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (input instanceof HTMLTextAreaElement) {
|
|
335
|
-
if (this.state.minLength !== undefined) input.minLength = this.state.minLength;
|
|
336
|
-
if (this.state.maxLength !== undefined) input.maxLength = this.state.maxLength;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
getValue(): string {
|
|
342
|
-
return this.state.value;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
getNumericValue(): number | null {
|
|
346
|
-
const num = Number(this.state.value);
|
|
347
|
-
return isNaN(num) ? null : num;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
isValid(): boolean {
|
|
351
|
-
return this._validate(this.state.value) === true;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/* -------------------------
|
|
355
|
-
* Render
|
|
356
|
-
* ------------------------- */
|
|
357
|
-
|
|
358
|
-
render(targetId?: string): this {
|
|
359
|
-
let container: HTMLElement;
|
|
215
|
+
protected _buildInputElement(): HTMLElement {
|
|
216
|
+
const { type, value, placeholder, required, disabled, name, rows, min, max, step, minLength, maxLength, pattern } = this.state;
|
|
360
217
|
|
|
361
|
-
if (targetId) {
|
|
362
|
-
const target = document.querySelector(targetId);
|
|
363
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
364
|
-
throw new Error(`Input: Target element "${targetId}" not found`);
|
|
365
|
-
}
|
|
366
|
-
container = target;
|
|
367
|
-
} else {
|
|
368
|
-
container = getOrCreateContainer(this._id);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
this.container = container;
|
|
372
|
-
const { type, value, placeholder, label, required, disabled, name, rows, min, max, step, minLength, maxLength, pattern, style, class: className } = this.state;
|
|
373
|
-
|
|
374
|
-
const wrapper = document.createElement('div');
|
|
375
|
-
wrapper.className = 'jux-input';
|
|
376
|
-
wrapper.id = this._id;
|
|
377
|
-
|
|
378
|
-
if (className) {
|
|
379
|
-
wrapper.className += ` ${className}`;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (style) {
|
|
383
|
-
wrapper.setAttribute('style', style);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Label
|
|
387
|
-
const labelEl = document.createElement('label');
|
|
388
|
-
labelEl.className = 'jux-input-label';
|
|
389
|
-
labelEl.htmlFor = `${this._id}-input`;
|
|
390
|
-
labelEl.textContent = label;
|
|
391
|
-
if (required) {
|
|
392
|
-
const requiredSpan = document.createElement('span');
|
|
393
|
-
requiredSpan.className = 'jux-input-required';
|
|
394
|
-
requiredSpan.textContent = ' *';
|
|
395
|
-
labelEl.appendChild(requiredSpan);
|
|
396
|
-
}
|
|
397
|
-
if (label) {
|
|
398
|
-
wrapper.appendChild(labelEl);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Input/Textarea
|
|
402
218
|
let inputEl: HTMLInputElement | HTMLTextAreaElement;
|
|
403
219
|
|
|
404
220
|
if (type === 'textarea') {
|
|
@@ -431,173 +247,157 @@ export class Input {
|
|
|
431
247
|
inputEl.required = required;
|
|
432
248
|
inputEl.disabled = disabled;
|
|
433
249
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const target = e.target as HTMLInputElement | HTMLTextAreaElement;
|
|
437
|
-
this.state.value = target.value;
|
|
250
|
+
return inputEl;
|
|
251
|
+
}
|
|
438
252
|
|
|
439
|
-
|
|
253
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
254
|
+
* RENDER
|
|
255
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
440
256
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
});
|
|
257
|
+
render(targetId?: string): this {
|
|
258
|
+
const container = this._setupContainer(targetId);
|
|
445
259
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
260
|
+
const { icon, maxLength, type, style, class: className } = this.state;
|
|
261
|
+
|
|
262
|
+
// Build wrapper
|
|
263
|
+
const wrapper = document.createElement('div');
|
|
264
|
+
wrapper.className = 'jux-input';
|
|
265
|
+
wrapper.id = this._id;
|
|
266
|
+
if (className) wrapper.className += ` ${className}`;
|
|
267
|
+
if (style) wrapper.setAttribute('style', style);
|
|
449
268
|
|
|
450
|
-
|
|
269
|
+
// Label
|
|
270
|
+
if (this.state.label) {
|
|
271
|
+
wrapper.appendChild(this._renderLabel());
|
|
272
|
+
}
|
|
451
273
|
|
|
452
|
-
//
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
274
|
+
// Input container
|
|
275
|
+
const inputContainer = document.createElement('div');
|
|
276
|
+
inputContainer.className = 'jux-input-container';
|
|
277
|
+
if (icon) inputContainer.classList.add('jux-input-with-icon');
|
|
278
|
+
|
|
279
|
+
// Icon
|
|
280
|
+
if (icon) {
|
|
281
|
+
const iconEl = document.createElement('span');
|
|
282
|
+
iconEl.className = 'jux-input-icon';
|
|
283
|
+
iconEl.appendChild(renderIcon(icon));
|
|
284
|
+
inputContainer.appendChild(iconEl);
|
|
285
|
+
}
|
|
458
286
|
|
|
459
|
-
//
|
|
287
|
+
// Input element
|
|
288
|
+
const inputEl = this._buildInputElement();
|
|
289
|
+
this._inputElement = inputEl;
|
|
290
|
+
inputContainer.appendChild(inputEl);
|
|
291
|
+
wrapper.appendChild(inputContainer);
|
|
292
|
+
|
|
293
|
+
// Error element
|
|
294
|
+
wrapper.appendChild(this._renderError());
|
|
295
|
+
|
|
296
|
+
// Character counter
|
|
460
297
|
if (maxLength && (type === 'text' || type === 'textarea')) {
|
|
461
298
|
const counterEl = document.createElement('div');
|
|
462
299
|
counterEl.className = 'jux-input-counter';
|
|
463
300
|
counterEl.id = `${this._id}-counter`;
|
|
464
|
-
counterEl.textContent = `${value.length}/${maxLength}`;
|
|
301
|
+
counterEl.textContent = `${this.state.value.length}/${maxLength}`;
|
|
302
|
+
wrapper.appendChild(counterEl);
|
|
465
303
|
|
|
466
304
|
inputEl.addEventListener('input', () => {
|
|
467
|
-
|
|
305
|
+
const input = inputEl as HTMLInputElement | HTMLTextAreaElement;
|
|
306
|
+
counterEl.textContent = `${input.value.length}/${maxLength}`;
|
|
307
|
+
this.state.value = input.value;
|
|
308
|
+
|
|
309
|
+
// 🎯 Fire the input callback event
|
|
310
|
+
this._triggerCallback('input', input.value);
|
|
468
311
|
});
|
|
312
|
+
} else {
|
|
313
|
+
// Fire input event even without counter
|
|
314
|
+
inputEl.addEventListener('input', () => {
|
|
315
|
+
const input = inputEl as HTMLInputElement | HTMLTextAreaElement;
|
|
316
|
+
this.state.value = input.value;
|
|
469
317
|
|
|
470
|
-
|
|
318
|
+
// 🎯 Fire the input callback event
|
|
319
|
+
this._triggerCallback('input', input.value);
|
|
320
|
+
});
|
|
471
321
|
}
|
|
472
322
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
wrapper.addEventListener(event, handler as EventListener);
|
|
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);
|
|
479
328
|
});
|
|
480
329
|
|
|
481
|
-
//
|
|
482
|
-
this.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
// Input → State (when input changes, update state)
|
|
503
|
-
inputEl.addEventListener('input', () => {
|
|
504
|
-
if (isUpdating) return;
|
|
505
|
-
isUpdating = true;
|
|
506
|
-
const transformed = transformToState(inputEl.value);
|
|
507
|
-
stateObj.set(transformed);
|
|
508
|
-
setTimeout(() => { isUpdating = false; }, 0);
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
else if (property === 'label') {
|
|
512
|
-
// Sync label
|
|
513
|
-
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
514
|
-
|
|
515
|
-
stateObj.subscribe((val: any) => {
|
|
516
|
-
const transformed = transformToComponent(val);
|
|
517
|
-
labelEl.textContent = transformed;
|
|
518
|
-
this.state.label = transformed;
|
|
519
|
-
});
|
|
330
|
+
// Wire events
|
|
331
|
+
this._wireStandardEvents(wrapper);
|
|
332
|
+
this._wireFormSync(inputEl, 'input');
|
|
333
|
+
|
|
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
|
+
}
|
|
342
|
+
|
|
343
|
+
container.appendChild(wrapper);
|
|
344
|
+
this._injectFormStyles();
|
|
345
|
+
|
|
346
|
+
requestAnimationFrame(() => {
|
|
347
|
+
if ((window as any).lucide) {
|
|
348
|
+
(window as any).lucide.createIcons();
|
|
520
349
|
}
|
|
521
350
|
});
|
|
522
351
|
|
|
523
352
|
return this;
|
|
524
353
|
}
|
|
354
|
+
}
|
|
525
355
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const style = document.createElement('style');
|
|
531
|
-
style.id = styleId;
|
|
532
|
-
style.textContent = `
|
|
533
|
-
.jux-input {
|
|
534
|
-
margin-bottom: 16px;
|
|
535
|
-
}
|
|
356
|
+
export function input(id: string, options: InputOptions = {}): Input {
|
|
357
|
+
return new Input(id, options);
|
|
358
|
+
}
|
|
536
359
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
color: #374151;
|
|
542
|
-
}
|
|
360
|
+
// Shorter, consistent naming
|
|
361
|
+
export function text(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
362
|
+
return new Input(id, { ...options, type: 'text' });
|
|
363
|
+
}
|
|
543
364
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
365
|
+
export function number(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
366
|
+
return new Input(id, { ...options, type: 'number' });
|
|
367
|
+
}
|
|
547
368
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
border: 1px solid #d1d5db;
|
|
552
|
-
border-radius: 6px;
|
|
553
|
-
font-size: 14px;
|
|
554
|
-
transition: border-color 0.2s;
|
|
555
|
-
box-sizing: border-box;
|
|
556
|
-
}
|
|
369
|
+
export function email(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
370
|
+
return new Input(id, { ...options, type: 'email' });
|
|
371
|
+
}
|
|
557
372
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
562
|
-
}
|
|
373
|
+
export function password(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
374
|
+
return new Input(id, { ...options, type: 'password' });
|
|
375
|
+
}
|
|
563
376
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
377
|
+
export function tel(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
378
|
+
return new Input(id, { ...options, type: 'tel' });
|
|
379
|
+
}
|
|
568
380
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
381
|
+
export function url(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
382
|
+
return new Input(id, { ...options, type: 'url' });
|
|
383
|
+
}
|
|
572
384
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
385
|
+
export function textarea(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
386
|
+
return new Input(id, { ...options, type: 'textarea' });
|
|
387
|
+
}
|
|
576
388
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
margin-top: 4px;
|
|
581
|
-
}
|
|
389
|
+
export function range(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
390
|
+
return new Input(id, { ...options, type: 'range' });
|
|
391
|
+
}
|
|
582
392
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
color: #6b7280;
|
|
587
|
-
margin-top: 4px;
|
|
588
|
-
}
|
|
589
|
-
`;
|
|
590
|
-
document.head.appendChild(style);
|
|
591
|
-
}
|
|
393
|
+
export function date(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
394
|
+
return new Input(id, { ...options, type: 'date' });
|
|
395
|
+
}
|
|
592
396
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
throw new Error('Input.renderTo: Invalid component');
|
|
596
|
-
}
|
|
597
|
-
return this.render(`#${juxComponent._id}`);
|
|
598
|
-
}
|
|
397
|
+
export function time(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
398
|
+
return new Input(id, { ...options, type: 'time' });
|
|
599
399
|
}
|
|
600
400
|
|
|
601
|
-
export function
|
|
602
|
-
return new Input(id, options);
|
|
401
|
+
export function color(id: string, options?: Omit<InputOptions, 'type'>): Input {
|
|
402
|
+
return new Input(id, { ...options, type: 'color' });
|
|
603
403
|
}
|