juxscript 1.1.211 → 1.1.212
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/lib/components/dataframe.d.ts +46 -0
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +158 -10
- package/lib/components/dataframe.ts +182 -11
- package/lib/components/fileupload.d.ts +19 -8
- package/lib/components/fileupload.d.ts.map +1 -1
- package/lib/components/fileupload.js +50 -39
- package/lib/components/fileupload.ts +70 -51
- package/lib/styles/shadcn.css +69 -0
- package/package.json +1 -1
|
@@ -20,6 +20,8 @@ export interface DataFrameOptions {
|
|
|
20
20
|
showReshapeWarning?: boolean;
|
|
21
21
|
style?: string;
|
|
22
22
|
class?: string;
|
|
23
|
+
persistToIndexedDB?: boolean;
|
|
24
|
+
clearStorageOnFileRemove?: boolean;
|
|
23
25
|
}
|
|
24
26
|
type DataFrameState = BaseState & {
|
|
25
27
|
loaded: boolean;
|
|
@@ -47,6 +49,14 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
47
49
|
private _rawFileData;
|
|
48
50
|
private _reshapeModal;
|
|
49
51
|
private _reshapeModalRendered;
|
|
52
|
+
private _persistToIndexedDB;
|
|
53
|
+
private _clearStorageOnFileRemove;
|
|
54
|
+
private _uploadButtonLabel;
|
|
55
|
+
private _uploadButtonIcon;
|
|
56
|
+
private _uploadButtonVariant;
|
|
57
|
+
private _uploadAccept;
|
|
58
|
+
private _uploadDescription;
|
|
59
|
+
private _showUploadIcon;
|
|
50
60
|
constructor(id: string, options?: DataFrameOptions);
|
|
51
61
|
protected getTriggerEvents(): readonly string[];
|
|
52
62
|
protected getCallbackEvents(): readonly string[];
|
|
@@ -54,6 +64,42 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
54
64
|
fromUpload(upload: FileUpload): this;
|
|
55
65
|
fromData(data: Record<string, any>[] | Record<string, any[]>): this;
|
|
56
66
|
withUpload(label?: string, accept?: string, icon?: string): this;
|
|
67
|
+
/**
|
|
68
|
+
* Set upload button label
|
|
69
|
+
*/
|
|
70
|
+
uploadLabel(label: string): this;
|
|
71
|
+
/**
|
|
72
|
+
* Set upload button icon
|
|
73
|
+
*/
|
|
74
|
+
uploadIcon(icon: string): this;
|
|
75
|
+
/**
|
|
76
|
+
* Set upload button variant (outline, primary, ghost, etc.)
|
|
77
|
+
*/
|
|
78
|
+
uploadVariant(variant: string): this;
|
|
79
|
+
/**
|
|
80
|
+
* Set accepted file types
|
|
81
|
+
*/
|
|
82
|
+
uploadAccept(accept: string): this;
|
|
83
|
+
/**
|
|
84
|
+
* Set description text below the upload button
|
|
85
|
+
*/
|
|
86
|
+
uploadDescription(description: string): this;
|
|
87
|
+
/**
|
|
88
|
+
* Show/hide upload icon
|
|
89
|
+
*/
|
|
90
|
+
showUploadIcon(show: boolean): this;
|
|
91
|
+
/**
|
|
92
|
+
* Enable/disable IndexedDB persistence (default: false = session only)
|
|
93
|
+
*/
|
|
94
|
+
persistToIndexedDB(enabled: boolean): this;
|
|
95
|
+
/**
|
|
96
|
+
* Clear stored data when file is removed (default: true)
|
|
97
|
+
*/
|
|
98
|
+
clearStorageOnFileRemove(enabled: boolean): this;
|
|
99
|
+
/**
|
|
100
|
+
* Clear the current data and reset the table
|
|
101
|
+
*/
|
|
102
|
+
clear(): Promise<this>;
|
|
57
103
|
showStatus(v: boolean): this;
|
|
58
104
|
statusIcon(v: string): this;
|
|
59
105
|
apply(fn: (df: DataFrame) => DataFrame): this;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AASnC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,16 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
39
39
|
this._rawFileData = null;
|
|
40
40
|
this._reshapeModal = null;
|
|
41
41
|
this._reshapeModalRendered = false;
|
|
42
|
+
// ✅ NEW: Storage configuration
|
|
43
|
+
this._persistToIndexedDB = false;
|
|
44
|
+
this._clearStorageOnFileRemove = true;
|
|
45
|
+
// ✅ NEW: Upload button styling
|
|
46
|
+
this._uploadButtonLabel = 'Upload File';
|
|
47
|
+
this._uploadButtonIcon = 'upload';
|
|
48
|
+
this._uploadButtonVariant = 'outline';
|
|
49
|
+
this._uploadAccept = '.csv,.tsv,.txt,.xlsx,.xls';
|
|
50
|
+
this._uploadDescription = '';
|
|
51
|
+
this._showUploadIcon = true;
|
|
42
52
|
this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
|
|
43
53
|
this._showStatus = options.showStatus ?? true;
|
|
44
54
|
this._icon = options.icon ?? '';
|
|
@@ -54,6 +64,9 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
54
64
|
this._sheetChunkSize = options.sheetChunkSize ?? 10000;
|
|
55
65
|
this._maxFileSize = options.maxFileSize ?? 50;
|
|
56
66
|
this._showReshapeWarning = options.showReshapeWarning ?? true;
|
|
67
|
+
// ✅ NEW: Storage options
|
|
68
|
+
this._persistToIndexedDB = options.persistToIndexedDB ?? false;
|
|
69
|
+
this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
|
|
57
70
|
}
|
|
58
71
|
getTriggerEvents() { return TRIGGER_EVENTS; }
|
|
59
72
|
getCallbackEvents() { return CALLBACK_EVENTS; }
|
|
@@ -124,6 +137,118 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
124
137
|
}
|
|
125
138
|
withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls', icon = 'upload') {
|
|
126
139
|
this._inlineUpload = { label, accept, icon };
|
|
140
|
+
this._uploadButtonLabel = label;
|
|
141
|
+
this._uploadAccept = accept;
|
|
142
|
+
this._uploadButtonIcon = icon;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Set upload button label
|
|
147
|
+
*/
|
|
148
|
+
uploadLabel(label) {
|
|
149
|
+
this._uploadButtonLabel = label;
|
|
150
|
+
if (this._inlineUpload)
|
|
151
|
+
this._inlineUpload.label = label;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Set upload button icon
|
|
156
|
+
*/
|
|
157
|
+
uploadIcon(icon) {
|
|
158
|
+
this._uploadButtonIcon = icon;
|
|
159
|
+
this._showUploadIcon = !!icon;
|
|
160
|
+
if (this._inlineUpload)
|
|
161
|
+
this._inlineUpload.icon = icon;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Set upload button variant (outline, primary, ghost, etc.)
|
|
166
|
+
*/
|
|
167
|
+
uploadVariant(variant) {
|
|
168
|
+
this._uploadButtonVariant = variant;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Set accepted file types
|
|
173
|
+
*/
|
|
174
|
+
uploadAccept(accept) {
|
|
175
|
+
this._uploadAccept = accept;
|
|
176
|
+
if (this._inlineUpload)
|
|
177
|
+
this._inlineUpload.accept = accept;
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Set description text below the upload button
|
|
182
|
+
*/
|
|
183
|
+
uploadDescription(description) {
|
|
184
|
+
this._uploadDescription = description;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Show/hide upload icon
|
|
189
|
+
*/
|
|
190
|
+
showUploadIcon(show) {
|
|
191
|
+
this._showUploadIcon = show;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/* ═══════════════════════════════════════════════════
|
|
195
|
+
* STORAGE OPTIONS (fluent API)
|
|
196
|
+
* ═══════════════════════════════════════════════════ */
|
|
197
|
+
/**
|
|
198
|
+
* Enable/disable IndexedDB persistence (default: false = session only)
|
|
199
|
+
*/
|
|
200
|
+
persistToIndexedDB(enabled) {
|
|
201
|
+
this._persistToIndexedDB = enabled;
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Clear stored data when file is removed (default: true)
|
|
206
|
+
*/
|
|
207
|
+
clearStorageOnFileRemove(enabled) {
|
|
208
|
+
this._clearStorageOnFileRemove = enabled;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
/* ═══════════════════════════════════════════════════
|
|
212
|
+
* CLEAR / RESET
|
|
213
|
+
* ═══════════════════════════════════════════════════ */
|
|
214
|
+
/**
|
|
215
|
+
* Clear the current data and reset the table
|
|
216
|
+
*/
|
|
217
|
+
async clear() {
|
|
218
|
+
// Clear storage if enabled
|
|
219
|
+
if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
|
|
220
|
+
try {
|
|
221
|
+
const tables = await this._driver.list();
|
|
222
|
+
const matchingTables = tables.filter(t => t.name === this._rawFileData.file.name);
|
|
223
|
+
for (const table of matchingTables) {
|
|
224
|
+
await this._driver.delete(table.id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
console.warn('[DataFrame] Failed to clear storage:', err);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Reset state
|
|
232
|
+
this._df = null;
|
|
233
|
+
this._rawFileData = null;
|
|
234
|
+
this._sheets.clear();
|
|
235
|
+
this.state.loaded = false;
|
|
236
|
+
this.state.sourceName = '';
|
|
237
|
+
this.state.rowCount = 0;
|
|
238
|
+
this.state.colCount = 0;
|
|
239
|
+
// Clear table display
|
|
240
|
+
if (this._table) {
|
|
241
|
+
this._table.columns([]).rows([]);
|
|
242
|
+
}
|
|
243
|
+
// Remove tabs if present
|
|
244
|
+
const wrapper = document.getElementById(this._id);
|
|
245
|
+
if (wrapper) {
|
|
246
|
+
const existingTabs = wrapper.querySelector('.jux-tabs');
|
|
247
|
+
if (existingTabs)
|
|
248
|
+
existingTabs.remove();
|
|
249
|
+
}
|
|
250
|
+
// Reset status
|
|
251
|
+
this._updateStatus('No data loaded.', 'empty');
|
|
127
252
|
return this;
|
|
128
253
|
}
|
|
129
254
|
/* ═══════════════════════════════════════════════════
|
|
@@ -193,7 +318,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
193
318
|
sheetChunkSize(v) { this._sheetChunkSize = v; return this; }
|
|
194
319
|
maxFileSize(mb) { this._maxFileSize = mb; return this; }
|
|
195
320
|
/* ═══════════════════════════════════════════════════
|
|
196
|
-
* FILE HANDLING
|
|
321
|
+
* FILE HANDLING (modified to use storage options)
|
|
197
322
|
* ═══════════════════════════════════════════════════ */
|
|
198
323
|
async _handleFile(file) {
|
|
199
324
|
const fileSizeMB = file.size / (1024 * 1024);
|
|
@@ -222,7 +347,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
222
347
|
this.state.loading = false;
|
|
223
348
|
return;
|
|
224
349
|
}
|
|
225
|
-
|
|
350
|
+
// ✅ Only store if persistence is enabled
|
|
351
|
+
if (this._persistToIndexedDB) {
|
|
352
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
353
|
+
}
|
|
226
354
|
if (sheetNames.length > 1) {
|
|
227
355
|
this._renderMultiSheet(sheets, file.name);
|
|
228
356
|
}
|
|
@@ -237,7 +365,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
237
365
|
autoDetectDelimiter: true,
|
|
238
366
|
hasHeader: true
|
|
239
367
|
});
|
|
240
|
-
|
|
368
|
+
// ✅ Only store if persistence is enabled
|
|
369
|
+
if (this._persistToIndexedDB) {
|
|
370
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
371
|
+
}
|
|
241
372
|
this._setDataFrame(df, file.name);
|
|
242
373
|
}
|
|
243
374
|
}
|
|
@@ -784,7 +915,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
784
915
|
});
|
|
785
916
|
}
|
|
786
917
|
/* ═══════════════════════════════════════════════════
|
|
787
|
-
* UPDATE & RENDER
|
|
918
|
+
* UPDATE & RENDER (modified for better upload styling)
|
|
788
919
|
* ═══════════════════════════════════════════════════ */
|
|
789
920
|
update(_prop, _value) { }
|
|
790
921
|
render(targetId) {
|
|
@@ -798,26 +929,43 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
798
929
|
if (style)
|
|
799
930
|
wrapper.setAttribute('style', style);
|
|
800
931
|
if (this._inlineUpload) {
|
|
932
|
+
// ✅ NEW: Enhanced upload area with better styling
|
|
933
|
+
const uploadArea = document.createElement('div');
|
|
934
|
+
uploadArea.className = 'jux-dataframe-upload-area';
|
|
935
|
+
uploadArea.id = `${this._id}-upload-area`;
|
|
801
936
|
const uploadOpts = {
|
|
802
|
-
label: this.
|
|
803
|
-
accept: this.
|
|
937
|
+
label: this._uploadButtonLabel,
|
|
938
|
+
accept: this._uploadAccept,
|
|
939
|
+
variant: this._uploadButtonVariant,
|
|
804
940
|
};
|
|
805
|
-
if (this.
|
|
806
|
-
uploadOpts.icon = this.
|
|
941
|
+
if (this._showUploadIcon && this._uploadButtonIcon) {
|
|
942
|
+
uploadOpts.icon = this._uploadButtonIcon;
|
|
807
943
|
}
|
|
808
944
|
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
809
945
|
this._uploadRef = upload;
|
|
946
|
+
// ✅ Handle file change AND file clear
|
|
810
947
|
this._pendingSource = async () => {
|
|
811
948
|
upload.bind('change', async (files) => {
|
|
812
|
-
if (!files || files.length === 0)
|
|
949
|
+
if (!files || files.length === 0) {
|
|
950
|
+
// File was cleared - reset the table
|
|
951
|
+
await this.clear();
|
|
813
952
|
return;
|
|
953
|
+
}
|
|
814
954
|
await this._handleFile(files[0]);
|
|
815
955
|
});
|
|
816
956
|
};
|
|
817
957
|
const uploadContainer = document.createElement('div');
|
|
818
958
|
uploadContainer.className = 'jux-dataframe-upload';
|
|
819
959
|
uploadContainer.id = `${this._id}-upload-container`;
|
|
820
|
-
|
|
960
|
+
uploadArea.appendChild(uploadContainer);
|
|
961
|
+
// ✅ Optional description text
|
|
962
|
+
if (this._uploadDescription) {
|
|
963
|
+
const descEl = document.createElement('div');
|
|
964
|
+
descEl.className = 'jux-dataframe-upload-description';
|
|
965
|
+
descEl.textContent = this._uploadDescription;
|
|
966
|
+
uploadArea.appendChild(descEl);
|
|
967
|
+
}
|
|
968
|
+
wrapper.appendChild(uploadArea);
|
|
821
969
|
container.appendChild(wrapper);
|
|
822
970
|
upload.render(uploadContainer);
|
|
823
971
|
}
|
|
@@ -28,6 +28,9 @@ export interface DataFrameOptions {
|
|
|
28
28
|
showReshapeWarning?: boolean;
|
|
29
29
|
style?: string;
|
|
30
30
|
class?: string;
|
|
31
|
+
// ✅ NEW: Storage options
|
|
32
|
+
persistToIndexedDB?: boolean; // Default: false (session only)
|
|
33
|
+
clearStorageOnFileRemove?: boolean; // Default: true
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
type DataFrameState = BaseState & {
|
|
@@ -64,6 +67,16 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
64
67
|
private _rawFileData: { file: File; text?: string; isExcel?: boolean } | null = null;
|
|
65
68
|
private _reshapeModal: Modal | null = null;
|
|
66
69
|
private _reshapeModalRendered: boolean = false;
|
|
70
|
+
// ✅ NEW: Storage configuration
|
|
71
|
+
private _persistToIndexedDB: boolean = false;
|
|
72
|
+
private _clearStorageOnFileRemove: boolean = true;
|
|
73
|
+
// ✅ NEW: Upload button styling
|
|
74
|
+
private _uploadButtonLabel: string = 'Upload File';
|
|
75
|
+
private _uploadButtonIcon: string = 'upload';
|
|
76
|
+
private _uploadButtonVariant: string = 'outline';
|
|
77
|
+
private _uploadAccept: string = '.csv,.tsv,.txt,.xlsx,.xls';
|
|
78
|
+
private _uploadDescription: string = '';
|
|
79
|
+
private _showUploadIcon: boolean = true;
|
|
67
80
|
|
|
68
81
|
constructor(id: string, options: DataFrameOptions = {}) {
|
|
69
82
|
super(id, {
|
|
@@ -98,6 +111,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
98
111
|
this._sheetChunkSize = options.sheetChunkSize ?? 10000;
|
|
99
112
|
this._maxFileSize = options.maxFileSize ?? 50;
|
|
100
113
|
this._showReshapeWarning = options.showReshapeWarning ?? true;
|
|
114
|
+
// ✅ NEW: Storage options
|
|
115
|
+
this._persistToIndexedDB = options.persistToIndexedDB ?? false;
|
|
116
|
+
this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
protected getTriggerEvents(): readonly string[] { return TRIGGER_EVENTS; }
|
|
@@ -159,8 +175,135 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
159
175
|
return this;
|
|
160
176
|
}
|
|
161
177
|
|
|
162
|
-
withUpload(
|
|
178
|
+
withUpload(
|
|
179
|
+
label: string = 'Upload File',
|
|
180
|
+
accept: string = '.csv,.tsv,.txt,.xlsx,.xls',
|
|
181
|
+
icon: string = 'upload'
|
|
182
|
+
): this {
|
|
163
183
|
this._inlineUpload = { label, accept, icon };
|
|
184
|
+
this._uploadButtonLabel = label;
|
|
185
|
+
this._uploadAccept = accept;
|
|
186
|
+
this._uploadButtonIcon = icon;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Set upload button label
|
|
192
|
+
*/
|
|
193
|
+
uploadLabel(label: string): this {
|
|
194
|
+
this._uploadButtonLabel = label;
|
|
195
|
+
if (this._inlineUpload) this._inlineUpload.label = label;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set upload button icon
|
|
201
|
+
*/
|
|
202
|
+
uploadIcon(icon: string): this {
|
|
203
|
+
this._uploadButtonIcon = icon;
|
|
204
|
+
this._showUploadIcon = !!icon;
|
|
205
|
+
if (this._inlineUpload) this._inlineUpload.icon = icon;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set upload button variant (outline, primary, ghost, etc.)
|
|
211
|
+
*/
|
|
212
|
+
uploadVariant(variant: string): this {
|
|
213
|
+
this._uploadButtonVariant = variant;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Set accepted file types
|
|
219
|
+
*/
|
|
220
|
+
uploadAccept(accept: string): this {
|
|
221
|
+
this._uploadAccept = accept;
|
|
222
|
+
if (this._inlineUpload) this._inlineUpload.accept = accept;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Set description text below the upload button
|
|
228
|
+
*/
|
|
229
|
+
uploadDescription(description: string): this {
|
|
230
|
+
this._uploadDescription = description;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Show/hide upload icon
|
|
236
|
+
*/
|
|
237
|
+
showUploadIcon(show: boolean): this {
|
|
238
|
+
this._showUploadIcon = show;
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ═══════════════════════════════════════════════════
|
|
243
|
+
* STORAGE OPTIONS (fluent API)
|
|
244
|
+
* ═══════════════════════════════════════════════════ */
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enable/disable IndexedDB persistence (default: false = session only)
|
|
248
|
+
*/
|
|
249
|
+
persistToIndexedDB(enabled: boolean): this {
|
|
250
|
+
this._persistToIndexedDB = enabled;
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Clear stored data when file is removed (default: true)
|
|
256
|
+
*/
|
|
257
|
+
clearStorageOnFileRemove(enabled: boolean): this {
|
|
258
|
+
this._clearStorageOnFileRemove = enabled;
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ═══════════════════════════════════════════════════
|
|
263
|
+
* CLEAR / RESET
|
|
264
|
+
* ═══════════════════════════════════════════════════ */
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Clear the current data and reset the table
|
|
268
|
+
*/
|
|
269
|
+
async clear(): Promise<this> {
|
|
270
|
+
// Clear storage if enabled
|
|
271
|
+
if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
|
|
272
|
+
try {
|
|
273
|
+
const tables = await this._driver.list();
|
|
274
|
+
const matchingTables = tables.filter(t => t.name === this._rawFileData!.file.name);
|
|
275
|
+
for (const table of matchingTables) {
|
|
276
|
+
await this._driver.delete(table.id);
|
|
277
|
+
}
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.warn('[DataFrame] Failed to clear storage:', err);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Reset state
|
|
284
|
+
this._df = null;
|
|
285
|
+
this._rawFileData = null;
|
|
286
|
+
this._sheets.clear();
|
|
287
|
+
this.state.loaded = false;
|
|
288
|
+
this.state.sourceName = '';
|
|
289
|
+
this.state.rowCount = 0;
|
|
290
|
+
this.state.colCount = 0;
|
|
291
|
+
|
|
292
|
+
// Clear table display
|
|
293
|
+
if (this._table) {
|
|
294
|
+
this._table.columns([]).rows([]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Remove tabs if present
|
|
298
|
+
const wrapper = document.getElementById(this._id);
|
|
299
|
+
if (wrapper) {
|
|
300
|
+
const existingTabs = wrapper.querySelector('.jux-tabs');
|
|
301
|
+
if (existingTabs) existingTabs.remove();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Reset status
|
|
305
|
+
this._updateStatus('No data loaded.', 'empty');
|
|
306
|
+
|
|
164
307
|
return this;
|
|
165
308
|
}
|
|
166
309
|
|
|
@@ -245,7 +388,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
245
388
|
maxFileSize(mb: number): this { this._maxFileSize = mb; return this; }
|
|
246
389
|
|
|
247
390
|
/* ═══════════════════════════════════════════════════
|
|
248
|
-
* FILE HANDLING
|
|
391
|
+
* FILE HANDLING (modified to use storage options)
|
|
249
392
|
* ═══════════════════════════════════════════════════ */
|
|
250
393
|
|
|
251
394
|
private async _handleFile(file: File): Promise<void> {
|
|
@@ -282,7 +425,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
282
425
|
return;
|
|
283
426
|
}
|
|
284
427
|
|
|
285
|
-
|
|
428
|
+
// ✅ Only store if persistence is enabled
|
|
429
|
+
if (this._persistToIndexedDB) {
|
|
430
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
431
|
+
}
|
|
286
432
|
|
|
287
433
|
if (sheetNames.length > 1) {
|
|
288
434
|
this._renderMultiSheet(sheets, file.name);
|
|
@@ -298,7 +444,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
298
444
|
hasHeader: true
|
|
299
445
|
});
|
|
300
446
|
|
|
301
|
-
|
|
447
|
+
// ✅ Only store if persistence is enabled
|
|
448
|
+
if (this._persistToIndexedDB) {
|
|
449
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
450
|
+
}
|
|
451
|
+
|
|
302
452
|
this._setDataFrame(df, file.name);
|
|
303
453
|
}
|
|
304
454
|
} catch (err: any) {
|
|
@@ -931,7 +1081,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
931
1081
|
}
|
|
932
1082
|
|
|
933
1083
|
/* ═══════════════════════════════════════════════════
|
|
934
|
-
* UPDATE & RENDER
|
|
1084
|
+
* UPDATE & RENDER (modified for better upload styling)
|
|
935
1085
|
* ═══════════════════════════════════════════════════ */
|
|
936
1086
|
|
|
937
1087
|
update(_prop: string, _value: any): void { }
|
|
@@ -947,20 +1097,31 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
947
1097
|
if (style) wrapper.setAttribute('style', style);
|
|
948
1098
|
|
|
949
1099
|
if (this._inlineUpload) {
|
|
1100
|
+
// ✅ NEW: Enhanced upload area with better styling
|
|
1101
|
+
const uploadArea = document.createElement('div');
|
|
1102
|
+
uploadArea.className = 'jux-dataframe-upload-area';
|
|
1103
|
+
uploadArea.id = `${this._id}-upload-area`;
|
|
1104
|
+
|
|
950
1105
|
const uploadOpts: any = {
|
|
951
|
-
label: this.
|
|
952
|
-
accept: this.
|
|
1106
|
+
label: this._uploadButtonLabel,
|
|
1107
|
+
accept: this._uploadAccept,
|
|
1108
|
+
variant: this._uploadButtonVariant,
|
|
953
1109
|
};
|
|
954
|
-
if (this.
|
|
955
|
-
uploadOpts.icon = this.
|
|
1110
|
+
if (this._showUploadIcon && this._uploadButtonIcon) {
|
|
1111
|
+
uploadOpts.icon = this._uploadButtonIcon;
|
|
956
1112
|
}
|
|
957
1113
|
|
|
958
1114
|
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
959
1115
|
this._uploadRef = upload;
|
|
960
1116
|
|
|
1117
|
+
// ✅ Handle file change AND file clear
|
|
961
1118
|
this._pendingSource = async () => {
|
|
962
1119
|
upload.bind('change', async (files: File[]) => {
|
|
963
|
-
if (!files || files.length === 0)
|
|
1120
|
+
if (!files || files.length === 0) {
|
|
1121
|
+
// File was cleared - reset the table
|
|
1122
|
+
await this.clear();
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
964
1125
|
await this._handleFile(files[0]);
|
|
965
1126
|
});
|
|
966
1127
|
};
|
|
@@ -968,7 +1129,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
968
1129
|
const uploadContainer = document.createElement('div');
|
|
969
1130
|
uploadContainer.className = 'jux-dataframe-upload';
|
|
970
1131
|
uploadContainer.id = `${this._id}-upload-container`;
|
|
971
|
-
|
|
1132
|
+
uploadArea.appendChild(uploadContainer);
|
|
1133
|
+
|
|
1134
|
+
// ✅ Optional description text
|
|
1135
|
+
if (this._uploadDescription) {
|
|
1136
|
+
const descEl = document.createElement('div');
|
|
1137
|
+
descEl.className = 'jux-dataframe-upload-description';
|
|
1138
|
+
descEl.textContent = this._uploadDescription;
|
|
1139
|
+
uploadArea.appendChild(descEl);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
wrapper.appendChild(uploadArea);
|
|
972
1143
|
container.appendChild(wrapper);
|
|
973
1144
|
upload.render(uploadContainer);
|
|
974
1145
|
} else {
|
|
@@ -4,22 +4,31 @@ export interface FileUploadOptions {
|
|
|
4
4
|
label?: string;
|
|
5
5
|
accept?: string;
|
|
6
6
|
multiple?: boolean;
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
name?: string;
|
|
9
7
|
icon?: string;
|
|
8
|
+
variant?: 'outline' | 'primary' | 'ghost' | 'secondary';
|
|
9
|
+
showFileList?: boolean;
|
|
10
|
+
maxFiles?: number;
|
|
11
|
+
maxFileSize?: number;
|
|
12
|
+
disabled?: boolean;
|
|
10
13
|
required?: boolean;
|
|
11
|
-
|
|
12
|
-
class?: string;
|
|
14
|
+
name?: string;
|
|
13
15
|
onValidate?: (files: File[]) => boolean | string;
|
|
14
16
|
storage?: FileStorage;
|
|
15
17
|
metadata?: Record<string, any>;
|
|
18
|
+
style?: string;
|
|
19
|
+
class?: string;
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
type FileUploadState = BaseState & {
|
|
22
|
+
label: string;
|
|
19
23
|
accept: string;
|
|
20
24
|
multiple: boolean;
|
|
21
25
|
icon: string;
|
|
22
|
-
|
|
26
|
+
variant: string;
|
|
27
|
+
showFileList: boolean;
|
|
28
|
+
maxFiles: number;
|
|
29
|
+
maxFileSize: number;
|
|
30
|
+
files: File[];
|
|
31
|
+
};
|
|
23
32
|
export declare class FileUpload extends BaseComponent<FileUploadState> {
|
|
24
33
|
private _fileListElement;
|
|
25
34
|
private _storage;
|
|
@@ -30,16 +39,18 @@ export declare class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
30
39
|
accept(value: string): this;
|
|
31
40
|
multiple(value: boolean): this;
|
|
32
41
|
icon(value: string): this;
|
|
42
|
+
variant(value: 'outline' | 'primary' | 'ghost' | 'secondary'): this;
|
|
33
43
|
storage(store: FileStorage, metadata?: Record<string, any>): this;
|
|
34
44
|
clear(): this;
|
|
45
|
+
clearFiles(): this;
|
|
35
46
|
getValue(): File[];
|
|
36
47
|
setValue(files: File[]): this;
|
|
37
48
|
validate(): boolean;
|
|
38
49
|
isValid(): boolean;
|
|
39
50
|
protected _validateValue(files: File[]): boolean | string;
|
|
40
|
-
protected _buildInputElement(): HTMLElement;
|
|
41
51
|
private _updateFileList;
|
|
42
52
|
private _formatFileSize;
|
|
53
|
+
update(prop: string, value: any): void;
|
|
43
54
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
44
55
|
}
|
|
45
56
|
export declare function fileupload(id: string, options?: FileUploadOptions): FileUpload;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAMxD,MAAM,WAAW,iBAAiB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["fileupload.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,11 @@ export class FileUpload extends BaseComponent {
|
|
|
20
20
|
files: [],
|
|
21
21
|
accept: options.accept ?? '',
|
|
22
22
|
multiple: options.multiple ?? false,
|
|
23
|
-
icon: options.icon ?? 'upload'
|
|
23
|
+
icon: options.icon ?? 'upload',
|
|
24
|
+
variant: options.variant ?? 'outline',
|
|
25
|
+
showFileList: options.showFileList ?? true,
|
|
26
|
+
maxFiles: options.maxFiles ?? 10,
|
|
27
|
+
maxFileSize: options.maxFileSize ?? 10,
|
|
24
28
|
});
|
|
25
29
|
this._fileListElement = null;
|
|
26
30
|
this._storage = null;
|
|
@@ -51,6 +55,10 @@ export class FileUpload extends BaseComponent {
|
|
|
51
55
|
this.state.icon = value;
|
|
52
56
|
return this;
|
|
53
57
|
}
|
|
58
|
+
variant(value) {
|
|
59
|
+
this.state.variant = value;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
54
62
|
storage(store, metadata) {
|
|
55
63
|
this._storage = store;
|
|
56
64
|
this._metadata = metadata;
|
|
@@ -67,6 +75,12 @@ export class FileUpload extends BaseComponent {
|
|
|
67
75
|
this._triggerCallback('clear', null, null, this);
|
|
68
76
|
return this;
|
|
69
77
|
}
|
|
78
|
+
clearFiles() {
|
|
79
|
+
this.state.files = [];
|
|
80
|
+
this._updateFileList();
|
|
81
|
+
this._triggerCallback('change', [], null, this);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
70
84
|
/* ═════════════════════════════════════════════════════════════════
|
|
71
85
|
* FORM INPUT IMPLEMENTATION
|
|
72
86
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -108,20 +122,6 @@ export class FileUpload extends BaseComponent {
|
|
|
108
122
|
}
|
|
109
123
|
return true;
|
|
110
124
|
}
|
|
111
|
-
_buildInputElement() {
|
|
112
|
-
const { accept, multiple, required, disabled, name } = this.state;
|
|
113
|
-
const input = document.createElement('input');
|
|
114
|
-
input.type = 'file';
|
|
115
|
-
input.className = 'jux-fileupload-input';
|
|
116
|
-
input.id = `${this._id}-input`;
|
|
117
|
-
input.name = name;
|
|
118
|
-
input.accept = accept;
|
|
119
|
-
input.multiple = multiple;
|
|
120
|
-
input.required = required;
|
|
121
|
-
input.disabled = disabled;
|
|
122
|
-
input.style.display = 'none';
|
|
123
|
-
return input;
|
|
124
|
-
}
|
|
125
125
|
_updateFileList() {
|
|
126
126
|
if (!this._fileListElement)
|
|
127
127
|
return;
|
|
@@ -157,49 +157,58 @@ export class FileUpload extends BaseComponent {
|
|
|
157
157
|
/* ═════════════════════════════════════════════════════════════════
|
|
158
158
|
* RENDER
|
|
159
159
|
* ═════════════════════════════════════════════════════════════════ */
|
|
160
|
+
update(prop, value) {
|
|
161
|
+
// No reactive updates needed
|
|
162
|
+
}
|
|
160
163
|
render(targetId) {
|
|
161
164
|
const container = this._setupContainer(targetId);
|
|
162
|
-
const { label, accept, multiple, icon, style, class: className } = this.state;
|
|
165
|
+
const { label, accept, multiple, icon, variant, showFileList, style, class: className } = this.state;
|
|
163
166
|
const wrapper = document.createElement('div');
|
|
164
|
-
wrapper.className = 'jux-
|
|
167
|
+
wrapper.className = 'jux-fileupload';
|
|
165
168
|
wrapper.id = this._id;
|
|
166
169
|
if (className)
|
|
167
170
|
wrapper.className += ` ${className}`;
|
|
168
171
|
if (style)
|
|
169
172
|
wrapper.setAttribute('style', style);
|
|
170
|
-
|
|
171
|
-
wrapper.appendChild(this._renderLabel());
|
|
172
|
-
}
|
|
173
|
-
// File input
|
|
173
|
+
// Hidden file input
|
|
174
174
|
const fileInput = document.createElement('input');
|
|
175
175
|
fileInput.type = 'file';
|
|
176
|
-
fileInput.id = `${this._id}-input`;
|
|
177
|
-
fileInput.style.display = 'none';
|
|
178
|
-
fileInput.multiple = multiple;
|
|
179
176
|
fileInput.accept = accept;
|
|
177
|
+
fileInput.multiple = multiple;
|
|
178
|
+
fileInput.style.display = 'none';
|
|
179
|
+
fileInput.id = `${this._id}-input`;
|
|
180
180
|
this._inputElement = fileInput;
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
181
|
+
// Button
|
|
182
|
+
const btn = document.createElement('button');
|
|
183
|
+
btn.className = `jux-fileupload-button jux-button-${variant}`;
|
|
184
|
+
btn.type = 'button';
|
|
184
185
|
if (icon) {
|
|
185
186
|
const iconEl = document.createElement('span');
|
|
186
187
|
iconEl.className = 'jux-fileupload-icon';
|
|
187
188
|
iconEl.appendChild(renderIcon(icon));
|
|
188
|
-
|
|
189
|
+
btn.appendChild(iconEl);
|
|
189
190
|
}
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
buttonContainer.
|
|
191
|
+
const labelSpan = document.createElement('span');
|
|
192
|
+
labelSpan.textContent = label;
|
|
193
|
+
btn.appendChild(labelSpan);
|
|
194
|
+
// Button container
|
|
195
|
+
const buttonContainer = document.createElement('div');
|
|
196
|
+
buttonContainer.className = 'jux-fileupload-button-container';
|
|
197
|
+
buttonContainer.appendChild(fileInput);
|
|
198
|
+
buttonContainer.appendChild(btn);
|
|
196
199
|
wrapper.appendChild(buttonContainer);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
// File list
|
|
201
|
+
if (showFileList) {
|
|
202
|
+
const fileList = document.createElement('div');
|
|
203
|
+
fileList.className = 'jux-fileupload-filelist';
|
|
204
|
+
this._fileListElement = fileList;
|
|
205
|
+
wrapper.appendChild(fileList);
|
|
206
|
+
}
|
|
207
|
+
// Error element
|
|
201
208
|
wrapper.appendChild(this._renderError());
|
|
202
|
-
button
|
|
209
|
+
// Wire up button click to trigger file input
|
|
210
|
+
btn.addEventListener('click', () => fileInput.click());
|
|
211
|
+
// Wire up file input change
|
|
203
212
|
fileInput.addEventListener('change', (e) => {
|
|
204
213
|
const files = Array.from(fileInput.files || []);
|
|
205
214
|
this.state.files = files;
|
|
@@ -216,11 +225,13 @@ export class FileUpload extends BaseComponent {
|
|
|
216
225
|
}
|
|
217
226
|
});
|
|
218
227
|
this._wireStandardEvents(wrapper);
|
|
228
|
+
// Validate on blur if already validated
|
|
219
229
|
fileInput.addEventListener('blur', () => {
|
|
220
230
|
if (this._hasBeenValidated) {
|
|
221
231
|
this.validate();
|
|
222
232
|
}
|
|
223
233
|
});
|
|
234
|
+
// Wire sync bindings for label
|
|
224
235
|
const labelSync = this._syncBindings.find(b => b.property === 'label');
|
|
225
236
|
if (labelSync) {
|
|
226
237
|
const transform = labelSync.toComponent || ((v) => String(v));
|
|
@@ -11,23 +11,32 @@ export interface FileUploadOptions {
|
|
|
11
11
|
label?: string;
|
|
12
12
|
accept?: string;
|
|
13
13
|
multiple?: boolean;
|
|
14
|
-
disabled?: boolean;
|
|
15
|
-
name?: string;
|
|
16
14
|
icon?: string;
|
|
15
|
+
variant?: 'outline' | 'primary' | 'ghost' | 'secondary';
|
|
16
|
+
showFileList?: boolean;
|
|
17
|
+
maxFiles?: number;
|
|
18
|
+
maxFileSize?: number;
|
|
19
|
+
disabled?: boolean;
|
|
17
20
|
required?: boolean;
|
|
18
|
-
|
|
19
|
-
class?: string;
|
|
21
|
+
name?: string;
|
|
20
22
|
onValidate?: (files: File[]) => boolean | string;
|
|
21
23
|
storage?: FileStorage;
|
|
22
24
|
metadata?: Record<string, any>;
|
|
25
|
+
style?: string;
|
|
26
|
+
class?: string;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
type FileUploadState = BaseState & {
|
|
30
|
+
label: string;
|
|
27
31
|
accept: string;
|
|
28
32
|
multiple: boolean;
|
|
29
33
|
icon: string;
|
|
30
|
-
|
|
34
|
+
variant: string;
|
|
35
|
+
showFileList: boolean;
|
|
36
|
+
maxFiles: number;
|
|
37
|
+
maxFileSize: number;
|
|
38
|
+
files: File[];
|
|
39
|
+
};
|
|
31
40
|
|
|
32
41
|
export class FileUpload extends BaseComponent<FileUploadState> {
|
|
33
42
|
private _fileListElement: HTMLElement | null = null;
|
|
@@ -49,7 +58,11 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
49
58
|
files: [],
|
|
50
59
|
accept: options.accept ?? '',
|
|
51
60
|
multiple: options.multiple ?? false,
|
|
52
|
-
icon: options.icon ?? 'upload'
|
|
61
|
+
icon: options.icon ?? 'upload',
|
|
62
|
+
variant: options.variant ?? 'outline',
|
|
63
|
+
showFileList: options.showFileList ?? true,
|
|
64
|
+
maxFiles: options.maxFiles ?? 10,
|
|
65
|
+
maxFileSize: options.maxFileSize ?? 10,
|
|
53
66
|
});
|
|
54
67
|
|
|
55
68
|
if (options.onValidate) {
|
|
@@ -87,6 +100,11 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
87
100
|
return this;
|
|
88
101
|
}
|
|
89
102
|
|
|
103
|
+
variant(value: 'outline' | 'primary' | 'ghost' | 'secondary'): this {
|
|
104
|
+
this.state.variant = value;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
storage(store: FileStorage, metadata?: Record<string, any>): this {
|
|
91
109
|
this._storage = store;
|
|
92
110
|
this._metadata = metadata;
|
|
@@ -105,6 +123,13 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
105
123
|
return this;
|
|
106
124
|
}
|
|
107
125
|
|
|
126
|
+
clearFiles(): this {
|
|
127
|
+
this.state.files = [];
|
|
128
|
+
this._updateFileList();
|
|
129
|
+
this._triggerCallback('change', [], null, this);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
108
133
|
/* ═════════════════════════════════════════════════════════════════
|
|
109
134
|
* FORM INPUT IMPLEMENTATION
|
|
110
135
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -155,23 +180,6 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
155
180
|
return true;
|
|
156
181
|
}
|
|
157
182
|
|
|
158
|
-
protected _buildInputElement(): HTMLElement {
|
|
159
|
-
const { accept, multiple, required, disabled, name } = this.state;
|
|
160
|
-
|
|
161
|
-
const input = document.createElement('input');
|
|
162
|
-
input.type = 'file';
|
|
163
|
-
input.className = 'jux-fileupload-input';
|
|
164
|
-
input.id = `${this._id}-input`;
|
|
165
|
-
input.name = name!;
|
|
166
|
-
input.accept = accept;
|
|
167
|
-
input.multiple = multiple;
|
|
168
|
-
input.required = required!;
|
|
169
|
-
input.disabled = disabled!;
|
|
170
|
-
input.style.display = 'none';
|
|
171
|
-
|
|
172
|
-
return input;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
183
|
private _updateFileList(): void {
|
|
176
184
|
if (!this._fileListElement) return;
|
|
177
185
|
|
|
@@ -213,59 +221,68 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
213
221
|
* RENDER
|
|
214
222
|
* ═════════════════════════════════════════════════════════════════ */
|
|
215
223
|
|
|
224
|
+
update(prop: string, value: any): void {
|
|
225
|
+
// No reactive updates needed
|
|
226
|
+
}
|
|
227
|
+
|
|
216
228
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this {
|
|
217
229
|
const container = this._setupContainer(targetId);
|
|
218
|
-
|
|
230
|
+
|
|
231
|
+
const { label, accept, multiple, icon, variant, showFileList, style, class: className } = this.state;
|
|
219
232
|
|
|
220
233
|
const wrapper = document.createElement('div');
|
|
221
|
-
wrapper.className = 'jux-
|
|
234
|
+
wrapper.className = 'jux-fileupload';
|
|
222
235
|
wrapper.id = this._id;
|
|
223
236
|
if (className) wrapper.className += ` ${className}`;
|
|
224
237
|
if (style) wrapper.setAttribute('style', style);
|
|
225
238
|
|
|
226
|
-
|
|
227
|
-
wrapper.appendChild(this._renderLabel());
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// File input
|
|
239
|
+
// Hidden file input
|
|
231
240
|
const fileInput = document.createElement('input');
|
|
232
241
|
fileInput.type = 'file';
|
|
233
|
-
fileInput.id = `${this._id}-input`;
|
|
234
|
-
fileInput.style.display = 'none';
|
|
235
|
-
fileInput.multiple = multiple;
|
|
236
242
|
fileInput.accept = accept;
|
|
237
|
-
|
|
243
|
+
fileInput.multiple = multiple;
|
|
244
|
+
fileInput.style.display = 'none';
|
|
245
|
+
fileInput.id = `${this._id}-input`;
|
|
238
246
|
this._inputElement = fileInput;
|
|
239
|
-
wrapper.appendChild(fileInput);
|
|
240
247
|
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
// Button
|
|
249
|
+
const btn = document.createElement('button');
|
|
250
|
+
btn.className = `jux-fileupload-button jux-button-${variant}`;
|
|
251
|
+
btn.type = 'button';
|
|
243
252
|
|
|
244
253
|
if (icon) {
|
|
245
254
|
const iconEl = document.createElement('span');
|
|
246
255
|
iconEl.className = 'jux-fileupload-icon';
|
|
247
256
|
iconEl.appendChild(renderIcon(icon));
|
|
248
|
-
|
|
257
|
+
btn.appendChild(iconEl);
|
|
249
258
|
}
|
|
250
259
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
button.textContent = 'Choose File(s)';
|
|
255
|
-
button.disabled = this.state.disabled!;
|
|
260
|
+
const labelSpan = document.createElement('span');
|
|
261
|
+
labelSpan.textContent = label;
|
|
262
|
+
btn.appendChild(labelSpan);
|
|
256
263
|
|
|
257
|
-
|
|
264
|
+
// Button container
|
|
265
|
+
const buttonContainer = document.createElement('div');
|
|
266
|
+
buttonContainer.className = 'jux-fileupload-button-container';
|
|
267
|
+
buttonContainer.appendChild(fileInput);
|
|
268
|
+
buttonContainer.appendChild(btn);
|
|
258
269
|
wrapper.appendChild(buttonContainer);
|
|
259
270
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
271
|
+
// File list
|
|
272
|
+
if (showFileList) {
|
|
273
|
+
const fileList = document.createElement('div');
|
|
274
|
+
fileList.className = 'jux-fileupload-filelist';
|
|
275
|
+
this._fileListElement = fileList;
|
|
276
|
+
wrapper.appendChild(fileList);
|
|
277
|
+
}
|
|
264
278
|
|
|
279
|
+
// Error element
|
|
265
280
|
wrapper.appendChild(this._renderError());
|
|
266
281
|
|
|
267
|
-
button
|
|
282
|
+
// Wire up button click to trigger file input
|
|
283
|
+
btn.addEventListener('click', () => fileInput.click());
|
|
268
284
|
|
|
285
|
+
// Wire up file input change
|
|
269
286
|
fileInput.addEventListener('change', (e) => {
|
|
270
287
|
const files = Array.from(fileInput.files || []);
|
|
271
288
|
this.state.files = files;
|
|
@@ -286,12 +303,14 @@ export class FileUpload extends BaseComponent<FileUploadState> {
|
|
|
286
303
|
|
|
287
304
|
this._wireStandardEvents(wrapper);
|
|
288
305
|
|
|
306
|
+
// Validate on blur if already validated
|
|
289
307
|
fileInput.addEventListener('blur', () => {
|
|
290
308
|
if (this._hasBeenValidated) {
|
|
291
309
|
this.validate();
|
|
292
310
|
}
|
|
293
311
|
});
|
|
294
312
|
|
|
313
|
+
// Wire sync bindings for label
|
|
295
314
|
const labelSync = this._syncBindings.find(b => b.property === 'label');
|
|
296
315
|
if (labelSync) {
|
|
297
316
|
const transform = labelSync.toComponent || ((v: any) => String(v));
|
package/lib/styles/shadcn.css
CHANGED
|
@@ -160,6 +160,75 @@ p { line-height: 1.75; color: hsl(var(--foreground)); }
|
|
|
160
160
|
width: 100%;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/* ✅ NEW: Enhanced upload area */
|
|
164
|
+
.jux-dataframe-upload-area {
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
align-items: flex-start;
|
|
168
|
+
gap: 0.5rem;
|
|
169
|
+
padding: 1rem;
|
|
170
|
+
background: hsl(var(--muted) / 0.2);
|
|
171
|
+
border: 1px dashed hsl(var(--border));
|
|
172
|
+
border-radius: var(--radius);
|
|
173
|
+
transition: all 0.2s;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.jux-dataframe-upload-area:hover {
|
|
177
|
+
background: hsl(var(--muted) / 0.3);
|
|
178
|
+
border-color: hsl(var(--muted-foreground) / 0.3);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.jux-dataframe-upload-area:has(.jux-fileupload-filelist:not(:empty)) {
|
|
182
|
+
background: hsl(var(--success) / 0.05);
|
|
183
|
+
border-color: hsl(var(--success) / 0.2);
|
|
184
|
+
border-style: solid;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.jux-dataframe-upload {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: 0.75rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.jux-dataframe-upload-description {
|
|
194
|
+
font-size: 0.8125rem;
|
|
195
|
+
color: hsl(var(--muted-foreground));
|
|
196
|
+
line-height: 1.4;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ✅ Style the fileupload button inside dataframe */
|
|
200
|
+
.jux-dataframe-upload .jux-fileupload {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 0.75rem;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.jux-dataframe-upload .jux-fileupload-button {
|
|
207
|
+
display: inline-flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 0.5rem;
|
|
210
|
+
padding: 0.5rem 1rem;
|
|
211
|
+
font-weight: 500;
|
|
212
|
+
border-radius: var(--radius);
|
|
213
|
+
transition: all 0.15s;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.jux-dataframe-upload .jux-fileupload-button:hover {
|
|
217
|
+
background: hsl(var(--accent));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* ✅ File list styling inside dataframe */
|
|
221
|
+
.jux-dataframe-upload .jux-fileupload-filelist {
|
|
222
|
+
margin-top: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.jux-dataframe-upload .jux-fileupload-item {
|
|
226
|
+
background: hsl(var(--background));
|
|
227
|
+
border: 1px solid hsl(var(--border));
|
|
228
|
+
padding: 0.5rem 0.75rem;
|
|
229
|
+
font-size: 0.8125rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
163
232
|
.jux-dataframe-status {
|
|
164
233
|
display: flex;
|
|
165
234
|
align-items: center;
|