juxscript 1.1.209 → 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.
@@ -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":"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"}
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
- await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
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
- await this._driver.store(file.name, df, { source: file.name });
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
  }
@@ -266,17 +397,23 @@ export class DataFrameComponent extends BaseComponent {
266
397
  this._sheets.set(name, df);
267
398
  });
268
399
  const sheetNames = Object.keys(sheets);
400
+ // Sanitize sheet names for use as DOM IDs
401
+ const sanitizeId = (name) => name.replace(/[^a-zA-Z0-9_-]/g, '_');
269
402
  const tabDefs = sheetNames.map(name => ({
270
- id: name,
403
+ id: sanitizeId(name),
271
404
  label: name,
272
405
  content: ''
273
406
  }));
274
407
  this._tabs = new Tabs(`${this._id}-tabs`, {
275
408
  tabs: tabDefs,
276
- activeTab: sheetNames[0]
409
+ activeTab: sanitizeId(sheetNames[0])
277
410
  });
411
+ // Map sanitized IDs back to original sheet names
412
+ const idToSheetName = new Map();
413
+ sheetNames.forEach(name => idToSheetName.set(sanitizeId(name), name));
278
414
  this._tabs.bind('tabChange', (tabId) => {
279
- this._df = this._sheets.get(tabId) || null;
415
+ const originalName = idToSheetName.get(tabId) || tabId;
416
+ this._df = this._sheets.get(originalName) || null;
280
417
  });
281
418
  const tabsContainer = document.createElement('div');
282
419
  tabsContainer.className = 'jux-dataframe-tabs';
@@ -284,7 +421,8 @@ export class DataFrameComponent extends BaseComponent {
284
421
  this._tabs.render(tabsContainer);
285
422
  sheetNames.forEach((sheetName) => {
286
423
  const df = sheets[sheetName];
287
- const table = new Table(`${this._id}-table-${sheetName}`, {
424
+ const safeId = sanitizeId(sheetName);
425
+ const table = new Table(`${this._id}-table-${safeId}`, {
288
426
  striped: this._tableOptions.striped,
289
427
  hoverable: this._tableOptions.hoverable,
290
428
  sortable: this._tableOptions.sortable,
@@ -294,13 +432,13 @@ export class DataFrameComponent extends BaseComponent {
294
432
  });
295
433
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
296
434
  table.columns(columnDefs).rows(df.toRows());
297
- const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
435
+ const settingsBtn = new Button(`${this._id}-settings-${safeId}`, {
298
436
  label: '⚙️ Import Settings',
299
437
  variant: 'ghost',
300
438
  size: 'small'
301
439
  });
302
440
  settingsBtn.bind('click', () => this._showReshapeModal());
303
- this._tabs.addTabContent(sheetName, [settingsBtn, table]);
441
+ this._tabs.addTabContent(safeId, [settingsBtn, table]);
304
442
  });
305
443
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
306
444
  this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
@@ -777,7 +915,7 @@ export class DataFrameComponent extends BaseComponent {
777
915
  });
778
916
  }
779
917
  /* ═══════════════════════════════════════════════════
780
- * UPDATE & RENDER
918
+ * UPDATE & RENDER (modified for better upload styling)
781
919
  * ═══════════════════════════════════════════════════ */
782
920
  update(_prop, _value) { }
783
921
  render(targetId) {
@@ -791,26 +929,43 @@ export class DataFrameComponent extends BaseComponent {
791
929
  if (style)
792
930
  wrapper.setAttribute('style', style);
793
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`;
794
936
  const uploadOpts = {
795
- label: this._inlineUpload.label,
796
- accept: this._inlineUpload.accept,
937
+ label: this._uploadButtonLabel,
938
+ accept: this._uploadAccept,
939
+ variant: this._uploadButtonVariant,
797
940
  };
798
- if (this._inlineUpload.icon) {
799
- uploadOpts.icon = this._inlineUpload.icon;
941
+ if (this._showUploadIcon && this._uploadButtonIcon) {
942
+ uploadOpts.icon = this._uploadButtonIcon;
800
943
  }
801
944
  const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
802
945
  this._uploadRef = upload;
946
+ // ✅ Handle file change AND file clear
803
947
  this._pendingSource = async () => {
804
948
  upload.bind('change', async (files) => {
805
- if (!files || files.length === 0)
949
+ if (!files || files.length === 0) {
950
+ // File was cleared - reset the table
951
+ await this.clear();
806
952
  return;
953
+ }
807
954
  await this._handleFile(files[0]);
808
955
  });
809
956
  };
810
957
  const uploadContainer = document.createElement('div');
811
958
  uploadContainer.className = 'jux-dataframe-upload';
812
959
  uploadContainer.id = `${this._id}-upload-container`;
813
- wrapper.appendChild(uploadContainer);
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);
814
969
  container.appendChild(wrapper);
815
970
  upload.render(uploadContainer);
816
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(label: string = 'Upload File', accept: string = '.csv,.tsv,.txt,.xlsx,.xls', icon: string = 'upload'): this {
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
- await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
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
- await this._driver.store(file.name, df, { source: file.name });
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) {
@@ -331,19 +481,27 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
331
481
 
332
482
  const sheetNames = Object.keys(sheets);
333
483
 
484
+ // Sanitize sheet names for use as DOM IDs
485
+ const sanitizeId = (name: string) => name.replace(/[^a-zA-Z0-9_-]/g, '_');
486
+
334
487
  const tabDefs = sheetNames.map(name => ({
335
- id: name,
488
+ id: sanitizeId(name),
336
489
  label: name,
337
490
  content: ''
338
491
  }));
339
492
 
340
493
  this._tabs = new Tabs(`${this._id}-tabs`, {
341
494
  tabs: tabDefs,
342
- activeTab: sheetNames[0]
495
+ activeTab: sanitizeId(sheetNames[0])
343
496
  });
344
497
 
498
+ // Map sanitized IDs back to original sheet names
499
+ const idToSheetName = new Map<string, string>();
500
+ sheetNames.forEach(name => idToSheetName.set(sanitizeId(name), name));
501
+
345
502
  this._tabs.bind('tabChange', (tabId: string) => {
346
- this._df = this._sheets.get(tabId) || null;
503
+ const originalName = idToSheetName.get(tabId) || tabId;
504
+ this._df = this._sheets.get(originalName) || null;
347
505
  });
348
506
 
349
507
  const tabsContainer = document.createElement('div');
@@ -354,8 +512,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
354
512
 
355
513
  sheetNames.forEach((sheetName) => {
356
514
  const df = sheets[sheetName];
515
+ const safeId = sanitizeId(sheetName);
357
516
 
358
- const table = new Table(`${this._id}-table-${sheetName}`, {
517
+ const table = new Table(`${this._id}-table-${safeId}`, {
359
518
  striped: this._tableOptions.striped,
360
519
  hoverable: this._tableOptions.hoverable,
361
520
  sortable: this._tableOptions.sortable,
@@ -367,14 +526,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
367
526
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
368
527
  table.columns(columnDefs).rows(df.toRows());
369
528
 
370
- const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
529
+ const settingsBtn = new Button(`${this._id}-settings-${safeId}`, {
371
530
  label: '⚙️ Import Settings',
372
531
  variant: 'ghost',
373
532
  size: 'small'
374
533
  });
375
534
  settingsBtn.bind('click', () => this._showReshapeModal());
376
535
 
377
- this._tabs!.addTabContent(sheetName, [settingsBtn, table]);
536
+ this._tabs!.addTabContent(safeId, [settingsBtn, table]);
378
537
  });
379
538
 
380
539
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
@@ -922,7 +1081,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
922
1081
  }
923
1082
 
924
1083
  /* ═══════════════════════════════════════════════════
925
- * UPDATE & RENDER
1084
+ * UPDATE & RENDER (modified for better upload styling)
926
1085
  * ═══════════════════════════════════════════════════ */
927
1086
 
928
1087
  update(_prop: string, _value: any): void { }
@@ -938,20 +1097,31 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
938
1097
  if (style) wrapper.setAttribute('style', style);
939
1098
 
940
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
+
941
1105
  const uploadOpts: any = {
942
- label: this._inlineUpload.label,
943
- accept: this._inlineUpload.accept,
1106
+ label: this._uploadButtonLabel,
1107
+ accept: this._uploadAccept,
1108
+ variant: this._uploadButtonVariant,
944
1109
  };
945
- if (this._inlineUpload.icon) {
946
- uploadOpts.icon = this._inlineUpload.icon;
1110
+ if (this._showUploadIcon && this._uploadButtonIcon) {
1111
+ uploadOpts.icon = this._uploadButtonIcon;
947
1112
  }
948
1113
 
949
1114
  const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
950
1115
  this._uploadRef = upload;
951
1116
 
1117
+ // ✅ Handle file change AND file clear
952
1118
  this._pendingSource = async () => {
953
1119
  upload.bind('change', async (files: File[]) => {
954
- if (!files || files.length === 0) return;
1120
+ if (!files || files.length === 0) {
1121
+ // File was cleared - reset the table
1122
+ await this.clear();
1123
+ return;
1124
+ }
955
1125
  await this._handleFile(files[0]);
956
1126
  });
957
1127
  };
@@ -959,7 +1129,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
959
1129
  const uploadContainer = document.createElement('div');
960
1130
  uploadContainer.className = 'jux-dataframe-upload';
961
1131
  uploadContainer.id = `${this._id}-upload-container`;
962
- wrapper.appendChild(uploadContainer);
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);
963
1143
  container.appendChild(wrapper);
964
1144
  upload.render(uploadContainer);
965
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
- style?: string;
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
- interface FileUploadState extends BaseState {
18
- files: File[];
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,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,MAAM,CAAC;IACjD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,UAAU,eAAgB,SAAQ,SAAS;IACvC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAW,SAAQ,aAAa,CAAC,eAAe,CAAC;IAC1D,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,SAAS,CAAkC;gBAEvC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IA0BvD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAQhD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK3B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAMjE,KAAK,IAAI,IAAI;IAgBb,QAAQ,IAAI,IAAI,EAAE;IAIlB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAM7B,QAAQ,IAAI,OAAO;IAcnB,OAAO,IAAI,OAAO;IAKlB,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,GAAG,MAAM;IAiBzD,SAAS,CAAC,kBAAkB,IAAI,WAAW;IAiB3C,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,eAAe;IAYvB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CAiGrE;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAElF"}
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-input jux-fileupload';
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
- if (this.state.label) {
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
- wrapper.appendChild(fileInput);
182
- const buttonContainer = document.createElement('div');
183
- buttonContainer.className = 'jux-fileupload-button-container';
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
- buttonContainer.appendChild(iconEl);
189
+ btn.appendChild(iconEl);
189
190
  }
190
- const button = document.createElement('button');
191
- button.type = 'button';
192
- button.className = 'jux-fileupload-button';
193
- button.textContent = 'Choose File(s)';
194
- button.disabled = this.state.disabled;
195
- buttonContainer.appendChild(button);
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
- const fileList = document.createElement('div');
198
- fileList.className = 'jux-fileupload-list';
199
- this._fileListElement = fileList;
200
- wrapper.appendChild(fileList);
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.addEventListener('click', () => fileInput.click());
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
- style?: string;
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
- interface FileUploadState extends BaseState {
26
- files: File[];
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
- const { label, accept, multiple, icon, style, class: className } = this.state;
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-input jux-fileupload';
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
- if (this.state.label) {
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
- const buttonContainer = document.createElement('div');
242
- buttonContainer.className = 'jux-fileupload-button-container';
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
- buttonContainer.appendChild(iconEl);
257
+ btn.appendChild(iconEl);
249
258
  }
250
259
 
251
- const button = document.createElement('button');
252
- button.type = 'button';
253
- button.className = 'jux-fileupload-button';
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
- buttonContainer.appendChild(button);
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
- const fileList = document.createElement('div');
261
- fileList.className = 'jux-fileupload-list';
262
- this._fileListElement = fileList;
263
- wrapper.appendChild(fileList);
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.addEventListener('click', () => fileInput.click());
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));
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.209",
3
+ "version": "1.1.212",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",