juxscript 1.1.80 → 1.1.81
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/dom-structure-map.json +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/lib/components/badge.d.ts.map +1 -1
- package/lib/components/badge.js +2 -1
- package/lib/components/badge.ts +2 -1
- package/lib/components/base/BaseComponent.d.ts +55 -1
- package/lib/components/base/BaseComponent.d.ts.map +1 -1
- package/lib/components/base/BaseComponent.js +168 -2
- package/lib/components/base/BaseComponent.ts +203 -3
- package/lib/components/checkbox.d.ts +5 -4
- package/lib/components/checkbox.d.ts.map +1 -1
- package/lib/components/checkbox.js +33 -16
- package/lib/components/checkbox.ts +39 -22
- package/lib/components/datepicker.d.ts +5 -4
- package/lib/components/datepicker.d.ts.map +1 -1
- package/lib/components/datepicker.js +31 -16
- package/lib/components/datepicker.ts +37 -22
- package/lib/components/dropdown.d.ts.map +1 -1
- package/lib/components/dropdown.js +2 -1
- package/lib/components/dropdown.ts +2 -1
- package/lib/components/fileupload.d.ts +6 -6
- package/lib/components/fileupload.d.ts.map +1 -1
- package/lib/components/fileupload.js +77 -52
- package/lib/components/fileupload.ts +88 -58
- package/lib/components/input.d.ts +5 -4
- package/lib/components/input.d.ts.map +1 -1
- package/lib/components/input.js +38 -24
- package/lib/components/input.ts +48 -33
- package/lib/components/radio.d.ts +5 -4
- package/lib/components/radio.d.ts.map +1 -1
- package/lib/components/radio.js +37 -14
- package/lib/components/radio.ts +40 -16
- package/lib/components/select.d.ts +5 -4
- package/lib/components/select.d.ts.map +1 -1
- package/lib/components/select.js +32 -11
- package/lib/components/select.ts +38 -16
- package/lib/components/switch.d.ts +5 -4
- package/lib/components/switch.d.ts.map +1 -1
- package/lib/components/switch.js +34 -11
- package/lib/components/switch.ts +42 -16
- package/lib/components/watcher.d.ts +195 -0
- package/lib/components/watcher.d.ts.map +1 -0
- package/lib/components/watcher.js +241 -0
- package/lib/components/watcher.ts +261 -0
- package/package.json +1 -1
- package/lib/components/base/FormInput.d.ts +0 -77
- package/lib/components/base/FormInput.d.ts.map +0 -1
- package/lib/components/base/FormInput.js +0 -171
- package/lib/components/base/FormInput.ts +0 -237
- package/lib/components/event-chain.ts +0 -31
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
2
|
import { renderIcon } from './icons.js';
|
|
3
|
+
import { formatIdAsLabel } from '../utils/formatId.js'; // ✅ Import
|
|
3
4
|
// Event definitions
|
|
4
5
|
const TRIGGER_EVENTS = [];
|
|
5
6
|
const CALLBACK_EVENTS = ['change', 'filesSelected', 'clear'];
|
|
6
|
-
export class FileUpload extends
|
|
7
|
+
export class FileUpload extends BaseComponent {
|
|
7
8
|
constructor(id, options = {}) {
|
|
8
9
|
super(id, {
|
|
10
|
+
visible: true,
|
|
11
|
+
disabled: options.disabled ?? false,
|
|
12
|
+
loading: false,
|
|
13
|
+
class: options.class ?? '',
|
|
14
|
+
style: options.style ?? '',
|
|
15
|
+
attributes: {},
|
|
16
|
+
label: options.label ?? formatIdAsLabel(id), // ✅ Auto-generate
|
|
17
|
+
required: options.required ?? false,
|
|
18
|
+
name: options.name ?? id,
|
|
19
|
+
errorMessage: undefined,
|
|
9
20
|
files: [],
|
|
10
21
|
accept: options.accept ?? '',
|
|
11
22
|
multiple: options.multiple ?? false,
|
|
12
|
-
icon: options.icon ?? 'upload'
|
|
13
|
-
label: options.label ?? '',
|
|
14
|
-
required: options.required ?? false,
|
|
15
|
-
disabled: options.disabled ?? false,
|
|
16
|
-
name: options.name ?? id,
|
|
17
|
-
style: options.style ?? '',
|
|
18
|
-
class: options.class ?? '',
|
|
19
|
-
errorMessage: undefined
|
|
23
|
+
icon: options.icon ?? 'upload'
|
|
20
24
|
});
|
|
21
25
|
this._fileListElement = null;
|
|
22
26
|
if (options.onValidate) {
|
|
@@ -32,12 +36,6 @@ export class FileUpload extends FormInput {
|
|
|
32
36
|
/* ═════════════════════════════════════════════════════════════════
|
|
33
37
|
* FLUENT API
|
|
34
38
|
* ═════════════════════════════════════════════════════════════════ */
|
|
35
|
-
// ✅ Inherited from FormInput/BaseComponent:
|
|
36
|
-
// - label(), required(), name(), onValidate()
|
|
37
|
-
// - validate(), isValid()
|
|
38
|
-
// - style(), class()
|
|
39
|
-
// - bind(), sync(), renderTo()
|
|
40
|
-
// - disabled(), enable(), disable()
|
|
41
39
|
accept(value) {
|
|
42
40
|
this.state.accept = value;
|
|
43
41
|
return this;
|
|
@@ -56,9 +54,8 @@ export class FileUpload extends FormInput {
|
|
|
56
54
|
this._inputElement.value = '';
|
|
57
55
|
}
|
|
58
56
|
if (this._fileListElement) {
|
|
59
|
-
this.
|
|
57
|
+
this._fileListElement.innerHTML = '';
|
|
60
58
|
}
|
|
61
|
-
// 🎯 Fire the clear callback event
|
|
62
59
|
this._triggerCallback('clear');
|
|
63
60
|
return this;
|
|
64
61
|
}
|
|
@@ -70,13 +67,25 @@ export class FileUpload extends FormInput {
|
|
|
70
67
|
}
|
|
71
68
|
setValue(files) {
|
|
72
69
|
this.state.files = files;
|
|
73
|
-
|
|
74
|
-
this._updateFileList(files);
|
|
75
|
-
}
|
|
70
|
+
this._updateFileList();
|
|
76
71
|
return this;
|
|
77
72
|
}
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
validate() {
|
|
74
|
+
this._hasBeenValidated = true;
|
|
75
|
+
const files = this.getValue();
|
|
76
|
+
const result = this._validateValue(files);
|
|
77
|
+
if (result === true) {
|
|
78
|
+
this._clearError();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this._showError(result);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
isValid() {
|
|
87
|
+
const files = this.getValue();
|
|
88
|
+
return this._validateValue(files) === true;
|
|
80
89
|
}
|
|
81
90
|
_validateValue(files) {
|
|
82
91
|
const { required } = this.state;
|
|
@@ -98,14 +107,45 @@ export class FileUpload extends FormInput {
|
|
|
98
107
|
input.className = 'jux-fileupload-input';
|
|
99
108
|
input.id = `${this._id}-input`;
|
|
100
109
|
input.name = name;
|
|
110
|
+
input.accept = accept;
|
|
111
|
+
input.multiple = multiple;
|
|
101
112
|
input.required = required;
|
|
102
113
|
input.disabled = disabled;
|
|
103
|
-
|
|
104
|
-
input.accept = accept;
|
|
105
|
-
if (multiple)
|
|
106
|
-
input.multiple = multiple;
|
|
114
|
+
input.style.display = 'none'; // Hidden, triggered by button
|
|
107
115
|
return input;
|
|
108
116
|
}
|
|
117
|
+
_updateFileList() {
|
|
118
|
+
if (!this._fileListElement)
|
|
119
|
+
return; // Safety check
|
|
120
|
+
this._fileListElement.innerHTML = '';
|
|
121
|
+
if (this.state.files.length === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.state.files.forEach((file, index) => {
|
|
125
|
+
const fileItem = document.createElement('div');
|
|
126
|
+
fileItem.className = 'jux-fileupload-item';
|
|
127
|
+
fileItem.innerHTML = `
|
|
128
|
+
<span class="jux-fileupload-filename">${file.name}</span>
|
|
129
|
+
<span class="jux-fileupload-filesize">${this._formatFileSize(file.size)}</span>
|
|
130
|
+
<button class="jux-fileupload-remove" data-index="${index}">×</button>
|
|
131
|
+
`;
|
|
132
|
+
const removeBtn = fileItem.querySelector('.jux-fileupload-remove');
|
|
133
|
+
removeBtn?.addEventListener('click', () => {
|
|
134
|
+
this.state.files = this.state.files.filter((_, i) => i !== index);
|
|
135
|
+
this._updateFileList();
|
|
136
|
+
this._triggerCallback('change', this.state.files);
|
|
137
|
+
});
|
|
138
|
+
this._fileListElement.appendChild(fileItem); // ✅ Non-null assertion safe here
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
_formatFileSize(bytes) {
|
|
142
|
+
if (bytes === 0)
|
|
143
|
+
return '0 Bytes';
|
|
144
|
+
const k = 1024;
|
|
145
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
146
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
147
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
148
|
+
}
|
|
109
149
|
/* ═════════════════════════════════════════════════════════════════
|
|
110
150
|
* RENDER
|
|
111
151
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -176,7 +216,7 @@ export class FileUpload extends FormInput {
|
|
|
176
216
|
isUpdating = true;
|
|
177
217
|
const files = Array.from(inputEl.files || []);
|
|
178
218
|
this.state.files = files;
|
|
179
|
-
this._updateFileList(
|
|
219
|
+
this._updateFileList();
|
|
180
220
|
this._clearError();
|
|
181
221
|
const transformed = transformToState(files);
|
|
182
222
|
stateObj.set(transformed);
|
|
@@ -191,7 +231,7 @@ export class FileUpload extends FormInput {
|
|
|
191
231
|
inputEl.addEventListener('change', () => {
|
|
192
232
|
const files = Array.from(inputEl.files || []);
|
|
193
233
|
this.state.files = files;
|
|
194
|
-
this._updateFileList(
|
|
234
|
+
this._updateFileList();
|
|
195
235
|
this._clearError();
|
|
196
236
|
// 🎯 Fire the callback events
|
|
197
237
|
this._triggerCallback('change', files);
|
|
@@ -200,7 +240,9 @@ export class FileUpload extends FormInput {
|
|
|
200
240
|
}
|
|
201
241
|
// Always add blur validation
|
|
202
242
|
inputEl.addEventListener('blur', () => {
|
|
203
|
-
this.
|
|
243
|
+
if (this._hasBeenValidated) {
|
|
244
|
+
this.validate();
|
|
245
|
+
}
|
|
204
246
|
});
|
|
205
247
|
// Sync label changes
|
|
206
248
|
const labelSync = this._syncBindings.find(b => b.property === 'label');
|
|
@@ -211,29 +253,12 @@ export class FileUpload extends FormInput {
|
|
|
211
253
|
});
|
|
212
254
|
}
|
|
213
255
|
container.appendChild(wrapper);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return;
|
|
219
|
-
this._fileListElement.innerHTML = '';
|
|
220
|
-
if (files.length === 0) {
|
|
221
|
-
this._fileListElement.textContent = 'No files selected';
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
files.forEach(file => {
|
|
225
|
-
const fileItem = document.createElement('div');
|
|
226
|
-
fileItem.className = 'jux-fileupload-item';
|
|
227
|
-
fileItem.textContent = `${file.name} (${this._formatFileSize(file.size)})`;
|
|
228
|
-
this._fileListElement.appendChild(fileItem);
|
|
256
|
+
requestAnimationFrame(() => {
|
|
257
|
+
if (window.lucide) {
|
|
258
|
+
window.lucide.createIcons();
|
|
259
|
+
}
|
|
229
260
|
});
|
|
230
|
-
|
|
231
|
-
_formatFileSize(bytes) {
|
|
232
|
-
if (bytes < 1024)
|
|
233
|
-
return `${bytes} B`;
|
|
234
|
-
if (bytes < 1024 * 1024)
|
|
235
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
236
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
261
|
+
return this;
|
|
237
262
|
}
|
|
238
263
|
}
|
|
239
264
|
export function fileupload(id, options = {}) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseComponent, BaseState } from './base/BaseComponent.js';
|
|
2
2
|
import { renderIcon } from './icons.js';
|
|
3
|
-
import {
|
|
3
|
+
import { formatIdAsLabel } from '../utils/formatId.js'; // ✅ Import
|
|
4
4
|
|
|
5
5
|
// Event definitions
|
|
6
6
|
const TRIGGER_EVENTS = [] as const;
|
|
@@ -19,29 +19,32 @@ export interface FileUploadOptions {
|
|
|
19
19
|
onValidate?: (files: File[]) => boolean | string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
interface FileUploadState extends
|
|
22
|
+
interface FileUploadState extends BaseState {
|
|
23
23
|
files: File[];
|
|
24
24
|
accept: string;
|
|
25
25
|
multiple: boolean;
|
|
26
26
|
icon: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export class FileUpload extends
|
|
29
|
+
export class FileUpload extends BaseComponent<FileUploadState> {
|
|
30
30
|
private _fileListElement: HTMLElement | null = null;
|
|
31
31
|
|
|
32
32
|
constructor(id: string, options: FileUploadOptions = {}) {
|
|
33
33
|
super(id, {
|
|
34
|
+
visible: true,
|
|
35
|
+
disabled: options.disabled ?? false,
|
|
36
|
+
loading: false,
|
|
37
|
+
class: options.class ?? '',
|
|
38
|
+
style: options.style ?? '',
|
|
39
|
+
attributes: {},
|
|
40
|
+
label: options.label ?? formatIdAsLabel(id), // ✅ Auto-generate
|
|
41
|
+
required: options.required ?? false,
|
|
42
|
+
name: options.name ?? id,
|
|
43
|
+
errorMessage: undefined,
|
|
34
44
|
files: [],
|
|
35
45
|
accept: options.accept ?? '',
|
|
36
46
|
multiple: options.multiple ?? false,
|
|
37
|
-
icon: options.icon ?? 'upload'
|
|
38
|
-
label: options.label ?? '',
|
|
39
|
-
required: options.required ?? false,
|
|
40
|
-
disabled: options.disabled ?? false,
|
|
41
|
-
name: options.name ?? id,
|
|
42
|
-
style: options.style ?? '',
|
|
43
|
-
class: options.class ?? '',
|
|
44
|
-
errorMessage: undefined
|
|
47
|
+
icon: options.icon ?? 'upload'
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
if (options.onValidate) {
|
|
@@ -61,13 +64,6 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
61
64
|
* FLUENT API
|
|
62
65
|
* ═════════════════════════════════════════════════════════════════ */
|
|
63
66
|
|
|
64
|
-
// ✅ Inherited from FormInput/BaseComponent:
|
|
65
|
-
// - label(), required(), name(), onValidate()
|
|
66
|
-
// - validate(), isValid()
|
|
67
|
-
// - style(), class()
|
|
68
|
-
// - bind(), sync(), renderTo()
|
|
69
|
-
// - disabled(), enable(), disable()
|
|
70
|
-
|
|
71
67
|
accept(value: string): this {
|
|
72
68
|
this.state.accept = value;
|
|
73
69
|
return this;
|
|
@@ -89,9 +85,8 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
89
85
|
(this._inputElement as HTMLInputElement).value = '';
|
|
90
86
|
}
|
|
91
87
|
if (this._fileListElement) {
|
|
92
|
-
this.
|
|
88
|
+
this._fileListElement.innerHTML = '';
|
|
93
89
|
}
|
|
94
|
-
// 🎯 Fire the clear callback event
|
|
95
90
|
this._triggerCallback('clear');
|
|
96
91
|
return this;
|
|
97
92
|
}
|
|
@@ -106,14 +101,27 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
106
101
|
|
|
107
102
|
setValue(files: File[]): this {
|
|
108
103
|
this.state.files = files;
|
|
109
|
-
|
|
110
|
-
this._updateFileList(files);
|
|
111
|
-
}
|
|
104
|
+
this._updateFileList();
|
|
112
105
|
return this;
|
|
113
106
|
}
|
|
114
107
|
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
validate(): boolean {
|
|
109
|
+
this._hasBeenValidated = true;
|
|
110
|
+
const files = this.getValue();
|
|
111
|
+
const result = this._validateValue(files);
|
|
112
|
+
|
|
113
|
+
if (result === true) {
|
|
114
|
+
this._clearError();
|
|
115
|
+
return true;
|
|
116
|
+
} else {
|
|
117
|
+
this._showError(result as string);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isValid(): boolean {
|
|
123
|
+
const files = this.getValue();
|
|
124
|
+
return this._validateValue(files) === true;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
protected _validateValue(files: File[]): boolean | string {
|
|
@@ -140,16 +148,53 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
140
148
|
input.type = 'file';
|
|
141
149
|
input.className = 'jux-fileupload-input';
|
|
142
150
|
input.id = `${this._id}-input`;
|
|
143
|
-
input.name = name
|
|
144
|
-
input.
|
|
145
|
-
input.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
input.name = name!;
|
|
152
|
+
input.accept = accept;
|
|
153
|
+
input.multiple = multiple;
|
|
154
|
+
input.required = required!;
|
|
155
|
+
input.disabled = disabled!;
|
|
156
|
+
input.style.display = 'none'; // Hidden, triggered by button
|
|
149
157
|
|
|
150
158
|
return input;
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
private _updateFileList(): void {
|
|
162
|
+
if (!this._fileListElement) return; // Safety check
|
|
163
|
+
|
|
164
|
+
this._fileListElement.innerHTML = '';
|
|
165
|
+
|
|
166
|
+
if (this.state.files.length === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.state.files.forEach((file, index) => {
|
|
171
|
+
const fileItem = document.createElement('div');
|
|
172
|
+
fileItem.className = 'jux-fileupload-item';
|
|
173
|
+
fileItem.innerHTML = `
|
|
174
|
+
<span class="jux-fileupload-filename">${file.name}</span>
|
|
175
|
+
<span class="jux-fileupload-filesize">${this._formatFileSize(file.size)}</span>
|
|
176
|
+
<button class="jux-fileupload-remove" data-index="${index}">×</button>
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
const removeBtn = fileItem.querySelector('.jux-fileupload-remove');
|
|
180
|
+
removeBtn?.addEventListener('click', () => {
|
|
181
|
+
this.state.files = this.state.files.filter((_, i) => i !== index);
|
|
182
|
+
this._updateFileList();
|
|
183
|
+
this._triggerCallback('change', this.state.files);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this._fileListElement!.appendChild(fileItem); // ✅ Non-null assertion safe here
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private _formatFileSize(bytes: number): string {
|
|
191
|
+
if (bytes === 0) return '0 Bytes';
|
|
192
|
+
const k = 1024;
|
|
193
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
194
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
195
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
196
|
+
}
|
|
197
|
+
|
|
153
198
|
/* ═════════════════════════════════════════════════════════════════
|
|
154
199
|
* RENDER
|
|
155
200
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -191,7 +236,7 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
191
236
|
button.type = 'button';
|
|
192
237
|
button.className = 'jux-fileupload-button';
|
|
193
238
|
button.textContent = 'Choose File(s)';
|
|
194
|
-
button.disabled = this.state.disabled
|
|
239
|
+
button.disabled = this.state.disabled!;
|
|
195
240
|
|
|
196
241
|
buttonContainer.appendChild(button);
|
|
197
242
|
wrapper.appendChild(buttonContainer);
|
|
@@ -236,7 +281,7 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
236
281
|
|
|
237
282
|
const files = Array.from(inputEl.files || []);
|
|
238
283
|
this.state.files = files;
|
|
239
|
-
this._updateFileList(
|
|
284
|
+
this._updateFileList();
|
|
240
285
|
this._clearError();
|
|
241
286
|
|
|
242
287
|
const transformed = transformToState(files);
|
|
@@ -253,7 +298,7 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
253
298
|
inputEl.addEventListener('change', () => {
|
|
254
299
|
const files = Array.from(inputEl.files || []);
|
|
255
300
|
this.state.files = files;
|
|
256
|
-
this._updateFileList(
|
|
301
|
+
this._updateFileList();
|
|
257
302
|
this._clearError();
|
|
258
303
|
|
|
259
304
|
// 🎯 Fire the callback events
|
|
@@ -264,7 +309,9 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
264
309
|
|
|
265
310
|
// Always add blur validation
|
|
266
311
|
inputEl.addEventListener('blur', () => {
|
|
267
|
-
this.
|
|
312
|
+
if (this._hasBeenValidated) {
|
|
313
|
+
this.validate();
|
|
314
|
+
}
|
|
268
315
|
});
|
|
269
316
|
|
|
270
317
|
// Sync label changes
|
|
@@ -277,31 +324,14 @@ export class FileUpload extends FormInput<FileUploadState> {
|
|
|
277
324
|
}
|
|
278
325
|
|
|
279
326
|
container.appendChild(wrapper);
|
|
280
|
-
return this;
|
|
281
|
-
}
|
|
282
327
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (files.length === 0) {
|
|
289
|
-
this._fileListElement.textContent = 'No files selected';
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
files.forEach(file => {
|
|
294
|
-
const fileItem = document.createElement('div');
|
|
295
|
-
fileItem.className = 'jux-fileupload-item';
|
|
296
|
-
fileItem.textContent = `${file.name} (${this._formatFileSize(file.size)})`;
|
|
297
|
-
this._fileListElement!.appendChild(fileItem);
|
|
328
|
+
requestAnimationFrame(() => {
|
|
329
|
+
if ((window as any).lucide) {
|
|
330
|
+
(window as any).lucide.createIcons();
|
|
331
|
+
}
|
|
298
332
|
});
|
|
299
|
-
}
|
|
300
333
|
|
|
301
|
-
|
|
302
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
303
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
304
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
334
|
+
return this;
|
|
305
335
|
}
|
|
306
336
|
}
|
|
307
337
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BaseComponent } from './base/BaseComponent.js';
|
|
1
|
+
import { BaseComponent, BaseState } from './base/BaseComponent.js';
|
|
3
2
|
export interface InputOptions {
|
|
4
3
|
type?: string;
|
|
5
4
|
value?: string;
|
|
@@ -20,7 +19,7 @@ export interface InputOptions {
|
|
|
20
19
|
style?: string;
|
|
21
20
|
class?: string;
|
|
22
21
|
}
|
|
23
|
-
interface InputState extends
|
|
22
|
+
interface InputState extends BaseState {
|
|
24
23
|
type: string;
|
|
25
24
|
value: string;
|
|
26
25
|
placeholder: string;
|
|
@@ -33,7 +32,7 @@ interface InputState extends FormInputState {
|
|
|
33
32
|
maxLength?: number;
|
|
34
33
|
pattern?: string;
|
|
35
34
|
}
|
|
36
|
-
export declare class Input extends
|
|
35
|
+
export declare class Input extends BaseComponent<InputState> {
|
|
37
36
|
constructor(id: string, options?: InputOptions);
|
|
38
37
|
protected getTriggerEvents(): readonly string[];
|
|
39
38
|
protected getCallbackEvents(): readonly string[];
|
|
@@ -51,6 +50,8 @@ export declare class Input extends FormInput<InputState> {
|
|
|
51
50
|
getValue(): string;
|
|
52
51
|
setValue(value: string): this;
|
|
53
52
|
getNumericValue(): number | null;
|
|
53
|
+
validate(): boolean;
|
|
54
|
+
isValid(): boolean;
|
|
54
55
|
protected _validateValue(value: string): boolean | string;
|
|
55
56
|
protected _buildInputElement(): HTMLElement;
|
|
56
57
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["input.ts"],"names":[],"mappings":"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"}
|
package/lib/components/input.js
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
2
|
import { renderIcon } from './icons.js';
|
|
3
|
+
import { formatIdAsLabel } from '../utils/formatId.js'; // ✅ Import
|
|
3
4
|
// Event definitions
|
|
4
5
|
const TRIGGER_EVENTS = [];
|
|
5
6
|
const CALLBACK_EVENTS = ['change', 'input'];
|
|
6
|
-
export class Input extends
|
|
7
|
+
export class Input extends BaseComponent {
|
|
7
8
|
constructor(id, options = {}) {
|
|
8
9
|
super(id, {
|
|
10
|
+
// ✅ BaseState properties
|
|
11
|
+
visible: true,
|
|
12
|
+
disabled: options.disabled ?? false,
|
|
13
|
+
loading: false,
|
|
14
|
+
class: options.class ?? '',
|
|
15
|
+
style: options.style ?? '',
|
|
16
|
+
attributes: {},
|
|
17
|
+
// ✅ Form-specific BaseState properties
|
|
18
|
+
label: options.label ?? formatIdAsLabel(id), // ✅ Auto-generate if not provided
|
|
19
|
+
required: options.required ?? false,
|
|
20
|
+
name: options.name ?? id,
|
|
21
|
+
errorMessage: undefined,
|
|
22
|
+
// ✅ Input-specific properties
|
|
9
23
|
type: options.type ?? 'text',
|
|
10
24
|
value: options.value ?? '',
|
|
11
25
|
placeholder: options.placeholder ?? '',
|
|
12
|
-
label: options.label ?? '', // ✅ Empty string = auto-generate in _renderLabel()
|
|
13
26
|
icon: options.icon ?? '',
|
|
14
|
-
required: options.required ?? false,
|
|
15
|
-
disabled: options.disabled ?? false,
|
|
16
|
-
name: options.name ?? id,
|
|
17
27
|
rows: options.rows ?? 3,
|
|
18
28
|
min: options.min,
|
|
19
29
|
max: options.max,
|
|
20
30
|
step: options.step,
|
|
21
31
|
minLength: options.minLength,
|
|
22
32
|
maxLength: options.maxLength,
|
|
23
|
-
pattern: options.pattern
|
|
24
|
-
style: options.style ?? '',
|
|
25
|
-
class: options.class ?? '',
|
|
26
|
-
errorMessage: undefined
|
|
33
|
+
pattern: options.pattern
|
|
27
34
|
});
|
|
28
35
|
if (options.onValidate) {
|
|
29
36
|
this._onValidate = options.onValidate;
|
|
@@ -38,12 +45,6 @@ export class Input extends FormInput {
|
|
|
38
45
|
/* ═════════════════════════════════════════════════════════════════
|
|
39
46
|
* FLUENT API
|
|
40
47
|
* ═════════════════════════════════════════════════════════════════ */
|
|
41
|
-
// ✅ Inherited from FormInput/BaseComponent:
|
|
42
|
-
// - label(), required(), name(), onValidate()
|
|
43
|
-
// - validate(), isValid()
|
|
44
|
-
// - style(), class()
|
|
45
|
-
// - bind(), sync(), renderTo()
|
|
46
|
-
// - disabled(), enable(), disable()
|
|
47
48
|
type(value) {
|
|
48
49
|
this.state.type = value;
|
|
49
50
|
return this;
|
|
@@ -88,7 +89,7 @@ export class Input extends FormInput {
|
|
|
88
89
|
return this;
|
|
89
90
|
}
|
|
90
91
|
/* ═════════════════════════════════════════════════════════════════
|
|
91
|
-
* FORM INPUT IMPLEMENTATION
|
|
92
|
+
* FORM INPUT IMPLEMENTATION (Override BaseComponent methods)
|
|
92
93
|
* ═════════════════════════════════════════════════════════════════ */
|
|
93
94
|
getValue() {
|
|
94
95
|
return this.state.value;
|
|
@@ -104,6 +105,23 @@ export class Input extends FormInput {
|
|
|
104
105
|
const num = Number(this.state.value);
|
|
105
106
|
return isNaN(num) ? null : num;
|
|
106
107
|
}
|
|
108
|
+
validate() {
|
|
109
|
+
this._hasBeenValidated = true;
|
|
110
|
+
const value = this.getValue();
|
|
111
|
+
const result = this._validateValue(value);
|
|
112
|
+
if (result === true) {
|
|
113
|
+
this._clearError();
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this._showError(result);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
isValid() {
|
|
122
|
+
const value = this.getValue();
|
|
123
|
+
return this._validateValue(value) === true;
|
|
124
|
+
}
|
|
107
125
|
_validateValue(value) {
|
|
108
126
|
const { required, type, min, max, minLength, maxLength, pattern } = this.state;
|
|
109
127
|
if (required && !value.trim()) {
|
|
@@ -188,7 +206,7 @@ export class Input extends FormInput {
|
|
|
188
206
|
* ═════════════════════════════════════════════════════════════════ */
|
|
189
207
|
render(targetId) {
|
|
190
208
|
const container = this._setupContainer(targetId);
|
|
191
|
-
const { icon, maxLength, type, style, class: className } = this.state;
|
|
209
|
+
const { icon, maxLength, type, style, class: className, label } = this.state;
|
|
192
210
|
// Build wrapper
|
|
193
211
|
const wrapper = document.createElement('div');
|
|
194
212
|
wrapper.className = 'jux-input';
|
|
@@ -197,8 +215,8 @@ export class Input extends FormInput {
|
|
|
197
215
|
wrapper.className += ` ${className}`;
|
|
198
216
|
if (style)
|
|
199
217
|
wrapper.setAttribute('style', style);
|
|
200
|
-
// Label
|
|
201
|
-
if (
|
|
218
|
+
// ✅ Label - always render if exists or can be auto-generated
|
|
219
|
+
if (label || !label) { // Always render label
|
|
202
220
|
wrapper.appendChild(this._renderLabel());
|
|
203
221
|
}
|
|
204
222
|
// Input container
|
|
@@ -231,23 +249,19 @@ export class Input extends FormInput {
|
|
|
231
249
|
const input = inputEl;
|
|
232
250
|
counterEl.textContent = `${input.value.length}/${maxLength}`;
|
|
233
251
|
this.state.value = input.value;
|
|
234
|
-
// 🎯 Fire the input callback event
|
|
235
252
|
this._triggerCallback('input', input.value);
|
|
236
253
|
});
|
|
237
254
|
}
|
|
238
255
|
else {
|
|
239
|
-
// Fire input event even without counter
|
|
240
256
|
inputEl.addEventListener('input', () => {
|
|
241
257
|
const input = inputEl;
|
|
242
258
|
this.state.value = input.value;
|
|
243
|
-
// 🎯 Fire the input callback event
|
|
244
259
|
this._triggerCallback('input', input.value);
|
|
245
260
|
});
|
|
246
261
|
}
|
|
247
262
|
// Fire change event on blur
|
|
248
263
|
inputEl.addEventListener('change', () => {
|
|
249
264
|
const input = inputEl;
|
|
250
|
-
// 🎯 Fire the change callback event
|
|
251
265
|
this._triggerCallback('change', input.value);
|
|
252
266
|
});
|
|
253
267
|
// Wire events
|